|
9553
|
184
|
33
|
2026-04-14T07:43:59.292235+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776152639292_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-5542000475283522072
|
-5542000475283522072
|
visual_change
|
hybrid
|
NULL
|
aws
iTerm2ShellEditViewSessionScriptsProfilesWindo aws
iTerm2ShellEditViewSessionScriptsProfilesWindowHelp(ahlSupport Daily - in 4h 17 m100% C7Tue 14 Apr 10:43:58PROD (-zsh)181DOCKER881DEV (-zsh)882DOCKER (docker-compose)35msDONEdocker_lamp_1docker_1amp_12026-04-14 07:43:01 Runnin1s DONEdocker_lamp_11 '/usr/local/bin/php'at1/fd/1' 2>&1docker_lamp_12026-04-14 07:43:03 Runnir1s DONEdocker_lamp_11 '/usr/local/bin/php' 'arc/1/fd/1'2>&1docker_lamp_12026-04-14 07:43:04 Runnirnts]1s DONEdocker_1amp_1'/usr/local/bin/php''ar*/proc/1/fd/1'2>&1docker_lamp_12026-04-14 07:43:05 Runnir1s DONEdocker_1amp_11 '/usr/local/bin/php' 'arc/1/fd/1' 2>&1docker_lamp_1 |2026-04-14 07:43:06 Runnirbatches=15]1S DONEdocker_lamp_11 '/usr/local/bin/php' *arS=15 >'/proc/1/fd/1' 2>&1docker_1amp_112026-04-14 07:43:08 Runnir-max-batches=15] in background1.07ms DONEdocker_lamp_1I ('/usr/local/bin/php'batches=15 › '/proc/1/fd/1' 2>&1 ; '/usr/loccmework/schedule-390defd641effba0f73a895e426dedocker_1amp_12026-04-14 07:43:08 Runnirly]8S DONEdocker_lamp_11 '/usr/local/bin/php' 'ar./proc/1/fd/1'2>&1docker_lamp_1docker_1amp_1docker_1amp_1RUNNINGdocker_lamp_104ms DONEdocker_1amp_1RUNNINGdocker_lamp_11s DONEAPP (-zsh)-zsh84-zsh-zsh86PROD (-zsh)Lastlogin:Sat Apr 11 11:13:58 on consoleawsAWS service@aws282~/jiminny/app/front-end/node_modules/@awsaws_crc32c.js883~/jiminny/app/front-end/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.jsaws_crc32c.js₴84~/jiminny/app/front-end/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.jsaws_crc32.js*5-/jiminny/app/front-end/node_modules/@aws-crypto/crc32/build/module/aws_crc32.jsaws_crc32.js986-/jiminny/app/front-end/node_modules/@aws-crypto/crc32/build/main/aws_crc32.jsgetAwsChunkedEncodingStream.js-/jiminny/app/front-end/node_modules/@smithy/util-stream/dist-es/getAwsChunkedEncodingStream.jsgetAwsChunkedEncodingStream.browser.js488-/jiminny/app/front-end/node_modules/@smithy/util-stream/dist-es/getAwsChunkedEncodingStream.bro.getAwsChunkedEncodingStream.js489-/jiminny/app/front-end/node_modules/@smithy/util-stream/dist-cjs/getAwsChunkedEncodingStream.jsT6 FE (-zsh)Last login: Sat Apr 11 12:38:35 on ttys004-zshO 87* Unable to acce...O x8in /Users/lukas or its parentsPRODin /Users/lukas or its parentsin /Users/lukas or its parentsin /Users/lukas or its parents.n /Users/lukas or its parentsin /Users/lukas or its parentsSTAGEstgort 22: Operation timed outin /Users/lukas or its parentsin /Users/lukas or its parentsrun_artisan_schedule: Done waiting for schedule:run2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEventsPoetry could not find a pyproject.toml file in /Users/lukas or its parents RONTEND2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEvents • 543.2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEventsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX T7 EXT (-zsh)2026-04-14 07:43:18 Jiminny Jobs\Calendar\SyncCalendarEventsPoetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.toml file in /Users/lukas or its parentsLukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|View in Docker Desktop• View ConfigEnable Watch...
|
9551
|
|
41255
|
877
|
4
|
2026-04-17T06:07:48.552377+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776406068552_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-5542000475283522072
|
-5542000475283522072
|
visual_change
|
hybrid
|
NULL
|
aws
FirefoxFileEditViewHistoryBookmarksProfilesToo aws
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpEU (ssh)DOCKERDEV (-zsh)-O $2APP (-zsh)883-zshDOCKER (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/infrastructure/dev/docker or its parents84-zsh®• ₴5X Y2 PROD (ssh)Run 'do-release-upgrade' to upgrade to it.Poetry could not find a pyproject.toml/docker or its parentsfilealukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/j$lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny~/J$ 0>0 lblA* Review screenpipe U...100% <Fri 17 Apr 9:07:48181•*6-zshPROD2.39.71.189UActivity Monitor.app/Applications/Utilities/Activity Monitor.appAWS serviceSequel Ace.app/Applications/Sequel Ace.appAnybox.app/Applications/Anybox.appMusic.app/Applications/Music.appArchive Utility.app/System/Library/CoreServices/Applications/Archive Utility.appKeychain Access.app/Applications/Utilities/Keychain Access.appAppFlowy.app/Applications/AppFlowy.appNumbers.app/Applications/Numbers.app282283984*5286₴7₴82.39.71.189in /Users/lukas or its parentsin /Users/lukas or its parentsSTAGEin /Users/lukas or its parents/Users/lukas or its parentsT6 FE (-zsh)Last login: Thu Apr 16 15:48:07on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parents RONTENDPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX T7 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|...
|
41252
|
|
44069
|
933
|
37
|
2026-04-17T08:32:04.567069+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776414724567_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-5542000475283522072
|
-5542000475283522072
|
visual_change
|
hybrid
|
NULL
|
aws
FirefoxFileEditViewHistoryBookmarksProfilesToo aws
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelpmeet.google.com/xpx-omah-rknall= Support Daily • in 3 h 28 m100% 1978 • Fri 17 Apr 11:32:04+awsllian Kyuchukov (Presenting, annotating)BraveViewProfilesto ourine ordier doee e© DEV Jiminmy• PROO J[URL_WITH_CREDENTIALS] FieldsD SaveoSchedule que02.24|02.25Activity Monitor.app/Applications/Utilities/Activity Monitor.appAWS serviceSequel Ace.app/Applications/Sequel Ace.appAnybox.app/Applications/Anybox.appMusic.app/Applications/Music.appArchive Utility.app/System/Library/CoreServices/Applications/Archive Utility.appKeychain Access.app/Applications/Utilities/Keychain Access.appAppFlowy.app/Applications/AppFlowy.appNumbers.app/Applications/Numbers.app282883₴84885₴6$87888(2826-64-17 82:28:82] production. DVFO: [0pportunityPendinof|Anolys|sA/terStogeChanged] No applicable terplates for opportunity ["∞pportunity_\&":1982198.*tєa_(d":1818) (*@rrelotton_Í&":*®1dc₫18f-9Гo5-4_"*code": 580, "response":"Internol Server Erroe") {"correlation_id*:*o1bk2ßeb-Be9e-4086-80cS-7879dMeceN7", "trace,(d" : "8ba33eb9-3623-4287-0048-78e823ec3837*)*) {"correlation_id":*a1b828eb-Beße-40t6-82c5-7879d44ece47", "trace_1d": "8b633eb9-3623-4287 -0048-78-023ec3837"}|(2826-04-17 82:20:07) production, INFO: (RunActivityAlAnolysisListener) Opportunity triggered Al Analysis {"contextObjectId":19862190, "contextObjectType" :"opportunity", "craTerplatef teldias*:[142,282, 143, 14...(2826-04-17 82:28:87) production. INF0: [RunActivityAiAnolysististener) Opportumity triggered Al Analysts ("contextObjectid": 19862190, 'contentObjecttype": "opportuníty", "cralerplatef teldtds*: [242,282, 143, 14.") ("correlation_id*:*albß28eb-Beße-4066-88c5-7879d4eCe47*, "troce_(d":"8ba33et9-3623-4287-0048-70e023ec3837*}Coolde preferences2024-11_.5.56.pngVasil Vasilevlian KyuchukovMihail MihaylovNikolay NikolovLukas Kovalik11:32 AM | Daily - Processing...
|
44067
|
|
78851
|
2019
|
7
|
2026-04-24T13:51:59.899234+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-24/1777 /Users/lukas/.screenpipe/data/data/2026-04-24/1777038719899_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-5542000475283522072
|
-5542000475283522072
|
visual_change
|
hybrid
|
NULL
|
aws
iTerm2ShellEditViewSessionScriptsProfilesWindo aws
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpAPP (-zsh)APP (-zsh)24O $4100% <78Fri 24 Apr 16:51:59DOCKER₴81Last login: Fri Apr 24 12:59:23 on ttys007DEV (-zsh)O $82*3screenpipe"Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml filelukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/J1.env.localawapp/Console/Commands/JiminnyDebugComrapp/Http/Controllers/API/ActivityContAWS serviceapp/Jobs/Team/SyncToIntercom.phpapp/Services/PlaybackService.phpconfig/logging.phpSwitchedto branch 'master'Your branch is behind 'origin/master'by 5 commits, and can be fast-forwarded.Cuse "git pull"to update your local branch)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ git pullremote: Enumerating objects: 50, done.remote: Counting objects: 100% (50/50),done.remote: Compressing objects: 100% (23/23), done.remote: Total 50 (delta 28), reused 48 (delta 27), pack-reused 0 (from 0)Unpacking objects: 100% (50/50), 8.46 KiB | 173.00 KiB/s, done.From github.com:jiminny/app+ ad8c8625c3...1ae95eb19e JY-20489-hudges-phase2e4a4800edc..ac10bb65b3 JY-20663-partner-rockeedd7e834d145..7b28fe8e0a JY-20738-debug-AJ-tracking-UP* [new branch]fix-fav-icon-and-forbid-claude-from-committing-> origin/JY-20489-hudges-phase2 (forced update)-> origin/JY-20663-partner-rockeed-> origin/JY-20738-debug-AJ-tracking-UP-› origin/fix-fav-icon-and-forbid-claude-from-committingUpdating 3ac70b38d8..e183237c25Fast-forwardfront-end/README.mdfront-end/jsconfig.jsonfront-end/package.jsonfront-end/src/__mocks__/setup.jsfront-end/src/components/AiReports/__tests_/__snapshots_/audio-player-modal.output.htmlfront-end/src/components/LiveCoach/VideoPlayer.vue.../src/components/Settings/shared/InviteMemberModal/__tests_/__snapshots__/InviteMemberModal.spec.js.snapfront-end/src/components/TeamInsights/Themes/__tests__/__snapshots__/Themes.spec.js.snapfront-end/src/components/layout/Sidebar/__tests_/__snapshots__/Sidebar.spec.js.snapfront-end/src/components/onboard/__tests_/__snapshots__/0nboard.spec.js.snapfront-end/src/components/playback/__tests__/__snapshots__/Playback.spec.js.snap10 ++-145+-48+-5+-2+-4+-26+-8front-end/src/components/playlists/__tests_/__snapshots__/Playlists.spec.js.snapfront-end/yarn.lock1 369613 files changed, 2206 insertions(+), 1691 deletions(-)lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co-b JY-20508-notify-before-AJ-report-expirationSwitched to a new branch 'JY-20508-notify-before-AJ-report-expiration'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20508-notify-before-AJ-report-expiration) $I-zsh*5APP...
|
78848
|
|
9863
|
189
|
41
|
2026-04-14T07:55:57.222148+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776153357222_m2.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
aws logus
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws logus","depth":1,"value":"aws logus","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-2332983839494589958
|
-7552414614265390273
|
visual_change
|
hybrid
|
NULL
|
aws logus
FirefoxFileEoitViewHistoryBookmarksProfi aws logus
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindow Helpdws- Import bookmarks..Sprint BoardSRD QueueGithuh- Platform Sprint 1 Q2 - Platform Tea(z) Configure SSH access to multiple e© Console Home | Console Home | usSecurityGroup | EC2 | us-east-2© JY-20543 add AJ reports User piloSRD-6779 | JY-20632 | Unable toJy 19798 evaluation for ai activity8 Jiminny8 Ask Jiminny test report - 8 Apr 20Service-Desk - Queues - PlatformJY-20543 add AJ reports User pilo% Configure SSH access to multiple «* New Tab- New labFirefoxSearch with Google or enter addressPlatform Sprint1 Q2 - Platfor...JY-20543 addAJ reports...MInbox (1,540) -lukas.kovalik...Meet - Daily -Jiminny203EC2 | us-east-2OWikipediaa Support Daily • in 4h 5 mA100% CS•Tue 14 Apr 10:55:5622°CNew York CityYouTube...
|
NULL
|
|
41256
|
878
|
6
|
2026-04-17T06:07:49.968250+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776406069968_m2.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
aws logus
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws logus","depth":1,"value":"aws logus","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-2332983839494589958
|
-7552414614265390273
|
visual_change
|
hybrid
|
NULL
|
aws logus
FirefoxFileEditViewHistoryBookmarks)Prof aws logus
FirefoxFileEditViewHistoryBookmarks)ProfilesToolsWindowHelpoedren wiin voocie or chter douresa$0.labl100% CFri 17 Apr 9:07:49—I Imnort bookmarks...Sprint BoardSRD Queuef Platform Sprint 2 Q2 - Platform Te:ISRD-67931 Les Mills activity typeNew TabSymfony|Component\Debug\Excer"Trace Details - 0388ce431d4c468New TabNew Tab— New TabGithuh22°CNew York CityFirefoxSearch with Google or enter addressPlatform Sprint2Q2 -...JiminnyJiminnyJY-20543 addAJ reports...MInbox (1,565) -Pipelines -jiminny/appBambooHRJiminny...
|
41254
|
|
78852
|
2020
|
6
|
2026-04-24T13:52:02.358195+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-24/1777 /Users/lukas/.screenpipe/data/data/2026-04-24/1777038722358_m2.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
aws logus
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws logus","depth":1,"value":"aws logus","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-2332983839494589958
|
-7552414614265390273
|
visual_change
|
hybrid
|
NULL
|
aws logus
rireroxcalMISTOMProtllesWindowmelprTavsc aws logus
rireroxcalMISTOMProtllesWindowmelprTavsco.sProledey© Automate> @ ProphetAiask-jiminny-report-expiring.blade.phpv @ Reports© Automateakeporscommand.onpc) AutomateakeporsketentionPollcyec Automateakeportssendcommano.oс) CrеацемоскaskJiminnykeporkesul© DeleteReportCommand.phpc) GenerateMarketingReport.phpC) Team.phpc) Usage.php07 Slack>W Teamsw Tracksa TranscriotionTwilioM Users)M Vocabulary• MZooml(c) CoachinaseedhackcUndatescActivities© Command.php© CreateDatabaseUsers.php© DatabaseTableCount.php© DeleteOldAiCrmNotesCommand.php© DeleressLentoverscommana.ongC DevPostmanCommano.ong(e nisrizaVinA DarticinantldantifiaationGor© chcrypulokenscommana.ongwreaturerlaosmeloer.onec) rixcrosslenantissues.phoc) FlushRolesPermissionscache.phoc) GeneratelnternalWebhookloken.phg€ GroupSetDefaultLanguageCommand.phC) ImoortRecordina.oho@ ImportUsersFromCsvFile.phpCIterateUsersCommand.ohvC)JiminnvCacheClearCommand.ohoC).liminnvDebuacommand.ohnC).liminnvSet5nervotedtiokenManaderMo© JiminnyTokeninfoCommand.phpC) MakeSlackl iveCoachinaChatNotesOn.r24 D366(C) ManadeScimForTeam.nhr(C) MarkBranchForEnvironmentPinelineCon(C) [EMAIL](c DhnAnm nhne Dronsanto@onchinaSoodhackGrontodA(e) DuraoCanferoncos nhnnamespace Jiminny Console conuse carbon carbontluse carbon carbonimmutablerusemluminate Suoport Colleduse Jiminny Jobs \AutomatedRepuse Jiminny Mail \Reports\Ask.use Jiminny Models \Automatedluse liminnv Models Team•Use Jlninny KeposttorLes Autluco liminnv Convicoc Winch Ause rsr log Loggerinterraceuse Throwable4 usagesclass Automatedkeportstommanc* Loq prefix for all locprivate const strina LOG* The name and sianaturd* avar strinanrotected Ssianatune =* Tho concolo command di* @var stringprotected $description =Ico --nonont-id +o m)JY-20489 | Optimize Nudges - PhaNew Tab• Al reports promotion pages by nikoJY-20738 add debug logs on AJ reJY-20157 add not enough activ X8 Jiminny« Userpliot Nuoge-createe0 Pipelines - jiminny/appInbox (1.609) - lukas kovalik@iimina Feed — jiminny — Sentry8 Jiminny8 503 Service Temporarily Unavail:@ applapo/Htto/Controllers/Fronten• github.comFiles8 86d7354Q Go to file.circlecicursor1 githubsonarlint•vscodeF appiaActionsComponentConfiguration• Console- CommandslActivitiesAnalvticeCalondars7Dealinsiahts• DevDialers7 FlasticcearchEngagementStats7GeckoFynortMiaratoPlavback| nemesPlavbooksDloulictePostmarkPronhetAF Reports(9 AutomatedReportsCommand.• AutomatedReportsRetention..app / app / Console / Commands / Reports / AutomateeportsCommand.ohoCode205 lines (169 1oc) • 7.28 KP* Execute the console command.* areturn 1n1public function handle(): intSthis->logger->info(self::L0G_PREFIX • ' Started');$this->disableExpiredAskJiminnyReports();snow = Lardon:.nowl),S1sweekend = Snow->1SWeekend();$isFirstDay0fMonth = $now->day === 1;$isManualTrigger = $this->option('report-id') !== null;// Check if the current month is a quarterly month (January, April, July, October)$isQuarterlyMonth = in_array($currentMonth, [1, 4, 7, 101, true);$this->logger->info(self::L0G_PREFIX. " Checking conditions', [ISweeKend = SIsweekend,'isFirstDav0fMonth' => SisFirstDav0fMonth."isOuarterlvMonth' = SicduarterlvMonth.// Process daily reports on weekdays only (skip Saturday/Sunday)./ Manual triggers via --report-1a bypass the weekend sk1p.if ( Sisweekend || SisManualTriager) <ISthis->processReports(AutomatedReportsService::FREQUENCY_DAILY):} else {Sthis->logger->info(self::L0G PREFIX . ' Skiopina daily revorts on weekend'):// Process weekly reports on Mondaysif (SisMondav) {|// Process monthly reports on the first day of the monthSthis->processReports(AutomatedReportsService::FREQUENCYMONTHLY):// Process quarterly reports on the first day of January, April, July, and Octoberif (SisFirstDav0fMonth && SisQuarterlvMonth)<|Sthis->processReports(AutomatedReportsService::FREQUENCY_QUARTERLY);1091 Top|A … 04 8 FiAr 1652:02symbolsFind definitions and references for functions and other symbols in thisfile by clicking a symbol below or in the code.= Filter symbolsmod Jiminnv\ConsolelComman…class AutomatedReportsComm..func _constructfunc handlefunc disableExpiredAskJiminny….func processReportsfunc getReportByld...
|
NULL
|
|
9862
|
188
|
32
|
2026-04-14T07:55:57.096716+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776153357096_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws logus
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws logus","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws logus","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-2332983839494589958
|
-7552414614265390273
|
visual_change
|
hybrid
|
NULL
|
aws logus
+SlackFileEditViewGoHistoryWindowHelpEDH aws logus
+SlackFileEditViewGoHistoryWindowHelpEDHomeDMsActivityFilesLater..•More+Jiminny ...= Unreadse ThreadsHuddlesDrafts & sentDirectoriesExternal connections* Starredplatform-inner-team(*Channels# ai-chapter# alerts# backend# confusion-clinic# curiosity_lab# engineering# frontend# general# infra-changes# jiminny-bg# platform-tickets# product _launches# random# releases# sofia-office# support# thank-yous→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan Georgiev• MessagesAdd canvasaws logul+AWS servicecom~apple~CloudDocsCloud DriveApplications/ApplicationsApplications~/ApplicationsApplications/System/ApplicationsApplications/System/Library/CoreServices/ApplicationsNikolay Yankov 10:41 AMможеш ли да я рьннеш ти командатаLukas Kovalik 10:43 AMдаNikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result нo e failedMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+AaSupport Daily - in 4h 5m100% <47Tue 14 Apr 10:55:568460282883884885286етьр за report template и д си пускамеNew...
|
NULL
|
|
44071
|
933
|
38
|
2026-04-17T08:32:07.556225+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776414727556_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws logus
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws logus","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws logus","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-2332983839494589958
|
-7552414614265390273
|
visual_change
|
hybrid
|
NULL
|
aws logus
FirefoxFileEditViewHistoryBookmarksProfi aws logus
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindow Helpmeet.google.com/xpx-omah-rkn‹$0= Support Daily • in 3 h 28 m100% 1978 • Fri 17 Apr 11:32:07=+llian Kyuchukov (Presenting, annotating)BraveProfilesuro ourine ordr crolduceoeDEV Jiminmy• PROD Jminehttps://us-east-2.console.awsO PROD EU JiminmyQ My PRsawsCloudWatchLogs InsightsLogs Insights QL% Query generatorQ FieldsD SaveoCancel• Completed. Query executed for 28 log groups. •Logs (1.4k)Patterns (4)|Logs (1.4k)Ptimestampaws logusAWS service@aws282-/jiminny/app/front-end/node_modules/@awsPSaws_crc32c.js~/jiminny/app/front-end/node_modules/@aws-crypto/crc32c/build/module/aws_crc32c.js283aws_crc32c.js~/jiminny/app/front-end/node_modules/@aws-crypto/crc32c/build/main/aws_crc32c.js₴84PSaws_crc32.js~/jiminny/app/front-end/node_modules/@aws-crypto/crc32/build/module/aws_crc32.js285PSaws_crc32.js~/jiminny/app/front-end/node_modules/@aws-crypto/crc32/build/main/aws_crc32.js₴6getAwsChunkedEncodingStream.js-/jiminny/app/front-end/node_modules/@smithy/util-stream/dist-es/getAwsChunkedEncodingStream.js$7getAwsChunkedEncodingStream.browser.js-/jiminny/app/front-end/node_modules/@smithy/util-stream/dist-es/getAwsChunkedEncodingStream.bro…488getAwsChunkedEncodingStream.js~/jiminny/app/front-end/node_modules/@smithy/util-stream/dist-cjs/getAwsChunkedEncodingStream.js4892026-04-17T02:28:86.29922026-04-17782:29:06.39822026-04-17702:20:07.30822026-04-17702:20:87.52222026-04-17T82:20:08.0062(2826-84-17 82:20:82) productton.INFO: (0pportunityPendingAiAnolysisAfterStogeChonged) No opplicable terplates for opportunity ("opportunity_td":19862190,"teom_id":1018) ("correlation_id":*01dcd18f-9fa5-4_",*code" :500, "response":"Internal Server Error") ("correlation_id*:*e1b62Beb-Be9e-40t6-80cS-7879dMece47", "troce_id": "8b033e59-3623-4287-0048-78e823ec3837*}|*) {"correlation_id":*o1b828eb-Beße-4ot6-88c5-7879d44ece47", "troce_id":"8ba33eb9-3623-4287-0048-70e823ec3837*)[2826-04-17 02:20:07) production.INFO: [RunActivityAiAnalysisListener) Opportunity triggered Al Analysis {"contextObjectId":19862190, "contextObjectType": "opportunity", "crTerplateFieldids": [142,282, 143, 14(2826-04-17 82:28:87) production. INF0: (RunActivityAiAnolysislistener) Opportunity triggered Al Anolysis ("'contextObjectid":19862190, *contextObjectType":"opportunity", "crnTerplateFieldids*: [142,202, 143,14.") ("correlation_id*:*a1b828eb-Beße-40t6-88cS-7879d4ece47*, "troce_id": *8ba33eb9-3623-4287-0048-78e023ec3837*}E GoudShellCookie preferences2024-11.6.56.pngWNNVasil Vasilevllian KyuchukovMihail MihaNikolay NikolovLukas Kovalik11:32 AM | Daily - Processing...
|
NULL
|
|
44070
|
934
|
22
|
2026-04-17T08:32:07.101867+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776414727101_m2.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
aws log
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws log","depth":1,"value":"aws log","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-4999561847541839372
|
-6079123064728055760
|
visual_change
|
hybrid
|
NULL
|
aws log
FirefoxFileEoitViewHistoryBookmarksProfile aws log
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindow Helpdws loeusGoogleaws logusDevelopers | HubSpotM Inbox (1,574) - lukas.kovalik@jimin|Al Modelllide esM 120216 is your HubSpot Log In CorG aws logus - Google SearchImages— New |aoaws> 0lablf Support Daily • in 3h 28 mA100% C•8 • Fri 17 Apr 11:32:06VideosForumsShort videosNewsMaro -Tools -AWSnvestigating HubSpotAListeners|PendingAnalysis|OpportunityPendingAiAnalysisAfterStageChanged:ate the opportunityunityrendingAiAnalysisatlerstagecnanged listener class In 4.205rielu valuesoer vice execule - uoes Il uoudle te opportunty mod.lllLiorsAiAnalysisAfterStageChanged does'te the opportunity. It's a trigger/orchestration step. Here's the full chain:203Amazon Web Services Logo, …..→ Locos-wore.netAWS is 10x slower than a dedi...• YouTubeAmazon Web Services - Wikip...W WikipediaShow more imagesAmazen =e/ consolenuiosconsole aws.amazon.comAWS Console - Amazon.comNo information is available for this page.Lear wnyMissing. togdsl onow resuits with. logusAllazon.comhttps://aws.amazon.com › consoleAWS Management ConsoleManage your AWS cloud resources easily through a web-based interface using the AWS ManagementConsolle.Anazon wyeo sewcesnuos.raws.amazon.com› olocs> oowered oy awsPowered by AWS Logos | AWS News BlogMar 27, 2007 — Head over to our Co-marketing With Amazon Web Services page and scroll down toLogo Program. From there you can download a Zip file with the logos.LOoemUenuos ooenuo coms cons awsAWS Free SVG, PNG, and vector downloadsAWS SVG Logos - A free Al / LLM model icon set covering major brands and models. Download SVG,PNG, and vectors.Amazon Web Serviceshttps://us-east-1.credentials.signin.awsAmazon Web ServicesIf vou click "Continue" or "Continue with Google / Apple / GitHub / Amazon". vou will sian in with an AWSwulcerbro access une cde cauon. rivacyMissing: logus | Show results with: logus1000 Logosmiosatoouogos.lel, alllazon-web selvites"ogoea event TireaingAiAnalysisAfterStageChanqed: :handle()cunhry Trom Dbhas AT CRM FILLING teature enableding CRM templatesfor the opportunityplicable template field IDsunupporcunltyAlAnalysis event queueortun1tyA1Analys1sL1scener::handle1CrmFieldValuesService: : execute()Voporruna Vernrle orandler.rexecure1. Calls Prophet AI to extract field values from deal2. Saves CrmTemplateRun records3. Dispatches AiAutomationAnalysisReadyJob (queued)- FrocessAlAucomaclonana lysiskesulcs→ calls service->syncOpportunityo to refresh local data- caLls UpdatecrmFleldAction:: executet)→ writes AI-generated values back to CRM (HubSpot)Unliwronously, but not stage_id - it writes Al-generated CRM field values (notnalysisResults:: getTargetObiect( calls service->syncOpportunity -Provsiskesulusono.bv - wnichre-suncste opporuhitrom =losoontage_id and other fields as a side effect:: syncRemoteRecord() also calls syncOpportunity after writing — UpdateCrmlly (no-op) if:_CRM_FILLING feature flagtch the opportunityve no active fieldshaes Issuealls inside ProcessAiAutomationAnalysisResults go through the normalen calls resolvestace → polentally inportstades, so this Al auromationnal trioger tor the reveated importStages calls vou re seeina, separate trom...
|
44068
|
|
9555
|
184
|
35
|
2026-04-14T07:44:05.360492+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776152645360_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws log
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws log","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws log","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-4999561847541839372
|
-6079123064728055760
|
visual_change
|
hybrid
|
NULL
|
aws log
iTerm2ShellEditViewSessionScriptsProfilesW aws log
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpDOCKER881DOCKER (docker-compose)docker_lamp_1docker_lamp_11s DONEdocker_lamp_11/fd/1'2>&1docker_lamp_1DEV (-zsh)882APP (-zsh)-zsh2026-04-14 07:43:01 Running ['artisan'meeting-bot:schedule-bot] ..l '/usr/local/bin/php''araws logl2026-04-14 07:43:03 Runnirdocker_1amp_11 '/usr/local/bin/php'AWS servicedocker_lamp_12026-04-14 07:43:04 Running L'artisan*Juminny: monitor-social-accoudocker_lamp_1*/proc/1/fd/1'docker_lamp_1'/usr/local/bin/php' 'artisan'jiminny:monitor-social-accounts >Running ['artisan'mailbox:skip-lists:refresh]docker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:skip-lists:refresh › */prodocker_lamp_12026-04-14 07:43:06 Running ['artisan' mailbox:batch:process --max-docker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batche*/proc/1/fd/1' 2>&1docker_lamp_12026-04-14 07:43:08 Running ['artisan' mailbox:batch:retry-failed --max-batches=15] in backgrounddocker_1amp_1• ('/usr/local/bin/php' 'artisan' mailbox:batch:retry-failedbatches=15 › '/proc/1/fd/1' 2>&1 ; '/usr/local/bin/php' 'artisan' schedule:finish "framework/schedule-390defd641effba0f73a895e426ded4cf2ba7f11" "$?") > '/dev/null' 2>&1 &docker_lamp_12026-04-14 07:43:08 Running ['artisan' calendar:sync --dateMode=daidocker_lamp_1/proc/1/fd/1'docker_lamp_1docker_lamp_1docker_1amp_1RUNNINGdocker_Lamp_104ms DONEdocker_lamp_1RUNNINGdocker_1amp_11S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'calendar: sync --dateMode=daily ›'run_artisan_schedule: Done waiting for schedule:run2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEvents2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEvents . 543.2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEvents2026-04-14 07:43:18 Jiminny\Jobs\Calendar\SyncCalendarEventsView in Docker Desktop• View ConfigEnable Watchla6lSupport Daily - in 4 h 16 mPROD (-zsh)₴4-zsh-zsh86PROD (-zsh)Last login:Sat Apr 11 11:13:58 on console-zsh100% C40 87Tue 14 Apr 10:44:04181* Unable to acce...O 88in /Users/lukas or its parentsin /Users/lukas or its parents-Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-JiminnyX T4 STAGE (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny$ stgssh: connect to host jiminny-stage-bastion port 22: Operation timed outlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $t5QA (-zsh)Last login: Sat Apr 11 12:38:35 on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsPRODSTAGEXT6 FE (-zsh)Last login: Sat Apr 11 12:38:35 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parents RONTENDPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX T7 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|...
|
9554
|
|
10614
|
210
|
30
|
2026-04-14T08:50:20.606154+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776156620606_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws log
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws log","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws log","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-4999561847541839372
|
-6079123064728055760
|
visual_change
|
hybrid
|
NULL
|
aws log
iTerm2ShellEditViewSessionScriptsProfilesW aws log
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpallSupport Daily - in 3 h 10 m100% <47Tue 14 Apr 11:50:20ec2-user@ip-10-30-93-249:~DOCKER0 ₴12026-04-1407:48:51]DEV (-zsh)882APP (-zsh)staging.INF0:[automated-reports]Started83ec2-user@ip-10-30-...84-zsh-zsh86-zshO ₴7.* Unable to acce...{"correlation_id":"4c37ea47-eebd-4122-8c35-9d6b9d707beb", "trace_id":"bfe6b131-e3ad-4cfc-8954-5fblecfded81"}O 8807:48:51]staging.INFO: [automated-reports]Checking conditions {"isMonday":false,"isFirstDay0fMonth": false, "currentMonth":4, "isQuarterlyMonth" :true} {"correlation_id":"4c37ea47-eebd-4122-8c35-9d6b9d707beb"id"."hfaGh121 a2nd Aafe e0ss sfhlacfdade1"?[2026-04-1407:48:51]fblecfded81"}staging.INF0: [automateaws logoeb", "trace_id":"bfe6b131-e3ad-4cfc-8954-5[2026-04-1407:48:51]c-8954-5fb1ecfded81"}staging. INFO: [automateA.AWS service16b9d707beb", "trace_id":"bfe6b131-e3ad-4cf[2026-04-14 07:48:51]staging. INFO: [automatedaily", "type": "ask_jiminny"} {"correlation_iccom~apple~CloudDocsCloud Drive282327-77235637a6f9","teamId" : 1, "frequency" :"[2026-04-14 07:48:51]staging.INFO: [automatedaily", "type": "ask_jiminny"} {"correlation_ic[2026-04-14 07:48:51] staging.INFO: [automateroot@fee51d2e1f17:/home/jiminny# [ec2-user@igny && bash"root@73b64f5d54a3:/home/jiminny# php artisan[2026-04-14 08:41:03] staging.INF0: [automate[2026-04-14 08:41:03]staging.INFO: [automateid":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"Applications883/ApplicationsApplications884~/ApplicationsApplications885/System/ApplicationsApplications₴6/System/Library/CoreServices/ApplicationsAutomatedReportsCommand.php$87~/jiminny/app/app/Console/Commands/Reports/AutomatedReportsCommand.php143-53453e6c4054","teamId" :1, "frequency" :"}"bfe6b131-e3ad-4cfc-8954-5fblecfded81"}-" I head -1) /bin/bash -c "cd /home/jimin94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"}:4, "isQuarterlyMonth":true} {"correlation_[2026-04-14 08:41:03]6abfc67e02f"}staging.INFO: [automated-reports]Processing daily reports {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8","trace_id":"94b4fdcc-f609-42e7-b5b7-b[2026-04-14 08:41:03J7-b5b7-b6abfc67e02f"}staging.INFO: [automated-reports] Found 3 daily reports to process{"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8","trace_id":"94b4fdcc-f609-42e[2026-04-14 08:41:03]staging.INF0: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"fa7417aa-538e-49ab-8827-77235637a6f9", "teamId" :1, "frequency" : "daily", "type": "ask_jiminny"} {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"',"trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f**}[2026-04-1408:41:037staging.INFO: [automated-reports]daily"Dispatching Generate Report job for report {"reportUuid": "63e6d70b-b7cb-4dfa-8443-53453e6c4054", "teamId" :1, "frequency" : ","type": "ask_jiminny"} {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"',"trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"*}[2026-04-14 08:41:04]staging.INFO: [automated-reports]Dispatching Generate Reportjob for report {"reportUuid":"7e7846e2-c0ea-4040-88f4-0ae14b66ade8""teamId" :1, "frequency":"daily", "type": "ask_jiminny"} {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"',"trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"})[2026-04-14 08:41:04] staging.INFO: [automated-reports]Completed{"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8", "trace_id" : "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"'}root@73b64f5d54a3:/home/jiminny#...
|
10612
|
|
9554
|
184
|
34
|
2026-04-14T07:44:02.331343+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776152642331_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws i
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws i","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws i","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
-2108159934355365679
|
4974286016573472541
|
visual_change
|
hybrid
|
NULL
|
aws i
iTerm2ShellEditViewSessionScriptsProfilesWin aws i
iTerm2ShellEditViewSessionScriptsProfilesWindowHelp• 0DOCKER881DOCKER (docker-compose)35msDONEdocker_lamp_1docker_1amp_11S DONEdocker_lamp_11/fd/1' 2>&1docker_lamp_11s DONEdocker_lamp_1c/1/fd/1'2>&1docker_lamp_1DEV (-zsh)882APP (-zsh)-zsh2026-04-14 07:43:01 Runnin1 '/usr/local/bin/php'*araws i2026-04-14 07:43:03 RunnirAWS service1 '/usr/local/bin/php' 'ar2026-04-14 07:43:04 Running['artisan' jiminny:monitor-social-accoudocker_1amp_1*/proc/1/fd/1'docker_lamp_1'/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts >2026-04-14 07:43:05 Running ['artisan' mailbox:skip-lists:refresh]docker_1amp_11 '/usr/local/bin/php' 'artisan' mailbox:skip-lists:refresh › '/proc/1/fd/1' 2>&1docker_lamp_12026-04-14 07:43:06 Running ['artisan' mailbox:batch:process --max-batches=15]1S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batcheS=15 >'/proc/1/fd/1' 2>&1docker_1amp_12026-04-14 07:43:08 Running['artisan'mailbox: batch:retry-failed --max-batches=15] in background1.07ms DONEdocker_lamp_1• ('/usr/local/bin/php' 'artisan'mailbox:batch:retry-failed--max-batches=15 >'/proc/1/fd/1' 2>&1 ; '/usr/local/bin/php' 'artisan' schedule:finish "framework/schedule-390defd641effba0f73a895e426ded4cf2ba7f11" "$?") > '/dev/null' 2>&1 &docker_1amp_12026-04-14 07:43:08 Running ['artisan' calendar:sync --dateMode=daily]8S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' calendar:sync --dateMode=daily › •/proc/1/fd/1'2>&1docker_lamp_1docker_1amp_1docker_lamp_1run_artisan_schedule: Done waitingfor schedule: run2026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEventsRUNNINGdocker_lamp_12026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEvents • 543.04ms DONEdocker_1amp_12026-04-14 07:43:17 Jiminny\Jobs\Calendar\SyncCalendarEventsRUNNINGdocker_lamp_12026-04-14 07:43:18Jiminny\Jobs\Calendar\SyncCalendarEvents1S DONEView in Docker DesktopView ConfigEnable Watch(aolSupport Daily - in 4 h 16 mPROD (-zsh)₴4-zsh-zsh86T2 PROD (-zsh)Last login:Sat Apr 11 11:13:58 on console-zsh100% C40 87Tue 14 Apr 10:44:01181* Unable to acce...O 88in /Users/lukas or its parentsin /Users/lukas or its parents-Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-JiminnyX T4 STAGE (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny$ stgssh: connect to host jiminny-stage-bastion port 22: Operation timed outlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~$X Y5QA (-zsh)Last login: Sat Apr 11 12:38:35 on ttys003Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsPRODSTAGET6 FE (-zsh)Last login: Sat Apr 11 12:38:35 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parents RONTENDPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX T7 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ [|...
|
NULL
|
|
10613
|
211
|
32
|
2026-04-14T08:50:15.348219+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776156615348_m2.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
aws cw
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws cw","depth":1,"value":"aws cw","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
5487192321686031488
|
-6283540007142054821
|
visual_change
|
hybrid
|
NULL
|
aws cw
FirefoxFileEoitViewHistoryBookmarksProfiles aws cw
FirefoxFileEoitViewHistoryBookmarksProfilesAWSToolsWindow HelpImport bookmarks...Sprint BoardSRD QueueGithuh~ Google Gemini..• XPlatform Sprint 1 Q2 - Platform TeaC JY-20543 add AJ reports User pilo2 Configure SSH access to multiple© Console Home | Console Home | usSecurityGroup | EC2 | us-east-2© JY-20543 add AJ reports User piloSRD-6779 | JY-20632 | Unable toJy 19798 evaluation for ai activity t8 Jiminny3 Ask Jiminny test report - 8 Apr 202- Service-Desk - Queues - PlatformJY-20543 add AJ reports User pilo* Configure SSH access to multiple «Ca CloudWatch | us-east-2€ New Tab+ New labwhy I can't see speakerdata on meemt in firefox.ho wto set it upShow thinking vDealing with missing features right when youneed them is incredibly frustrating. Assuming"meemt" means Google Meet, the reason youcan't see your speaker data comes down tohow Firefox interacts with Google's platformcompared to Chrome.Since "speaker data" can mean a couple ofdifferent things in a meeting, here are the twomost likely causes for your issue and exactlyhow to get things working.1. You Need "Speaker Notes" for GoogleSlidesThe Reality: Google Meet has a native featurethat lets you see your presentation's speakernotes right inside the video call interface.However, this relies on a specific "Present aTab" technology built exclusively for GoogleChrome. Firefox handles screen sharingdifferently, so that native integration simplywon't load.How to set it up (The Firefox Workaround):You can still present and read your notes inFirefox by manually managing your windows.I men in dia (arc) | can see myheadphones in both microphone andspeakersProvYour Jiminny chats aren't used to improve our models. Gemini is Alane can make mistakes, Inciualne aoout peodleYour privacy & GeminiSummarize pageFirefoxSearch with Google or enter addressPlatform Sprint1 Q2 - Platfor...JY-20543 addMInbox (1,540) -CloudWatch|us-east-2JiminnyMeet - Daily -Platform40loblSupport Daily - in 3h 10mA100% CS•Tue 14 Apr 11:50:1422°CNew York CityWikipediaYouTube...
|
10611
|
|
10612
|
210
|
29
|
2026-04-14T08:50:14.551350+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776156614551_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
aws c
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"aws c","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"aws c","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
8570887159546812111
|
-8364726088147246443
|
visual_change
|
hybrid
|
NULL
|
aws c
iTerm2ShellEditViewSessionScriptsProfilesWin aws c
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpallSupport Daily • in 3 h 10 m100% C 8Tue 14 Apr 11:50:14DOCKER• 812026-04-1407:48:51]DEV (-zsh)staging.INFO:882APP (-zsh)[automated-reports]Started83ec2-user@ip-10-30-93-249:~ec2-user@ip-10-30-...84-zsh-zsh86-zshO 87* Unable to acce...{"correlation_id":"4c37ea47-eebd-4122-8c35-9d6b9d707beb", "trace_id":"bfe6b131-e3ad-4cfc-8954-5fblecfded81"}O 88[2026-04-1407:48:51]staging.INFO: [automated-reports]Checking conditions {"isMonday":false,"isFirstDay0fMonth": false, "currentMonth":4, "isQuarterlyMonth" :true}id":"4c37ea47-eebd-4122-8c35-9d6b9d707beb'"tracsid"."hfoGh121 aand Aafe e0ss sfhlacfdade1"?{"correlation_[2026-04-1407:48:51]fblecfded81"}staging.INF0: [automateaws doeb", "trace_id":"bfe6b131-e3ad-4cfc-8954-5[2026-04-14 07:48:517c-8954-5fb1ecfded81"}staging. INFO: [automateAWS service16b9d707beb", "trace_id":"bfe6b131-e3ad-4cf[2026-04-14 07:48:51]staging. INFO:[automated-reports]Dispatching Generate Report job for report {"reportUuid":"fa7417aa-538e-49ab-8827-77235637a6f9".,"teamId" : 1, "frequency" :"daily", "type": "ask_jiminny"} {"correlation_id":"4c37ea47-eebd-4122-8c35-9d6b9d707beb", "trace_id": "bfe6b131-e3ad-4cfc-8954-5fblecfded81"}[2026-04-14 07:48:51]staging.INFO:[automated-reports]DispatchingGenerate Report job for report {"reportUuid":"63e6d70b-b7cb-4dfa-8443-53453e6c4054".,"teamId" :1, "frequency" :"daily","type": "ask_jiminny"}{"correlation_id": "4c37ea47-eebd-4122-8c35-9d6b9d707beb", "trace_id":"bfe6b131-e3ad-4cfc-8954-5fblecfded81"}[2026-04-14 07:48:51] staging.INFO: [automated-reports] Completed{"correlation_id":"4c37ea47-eebd-4122-8c35-9d6b9d707beb", "trace_id":"bfe6b131-e3ad-4cfc-8954-5fblecfded81"}root@fee51d2e1f17:/home/jiminny# [ec2-user@ip-10-30-93-249 ~]$ docker exec -it $(docker ps --format "{{.ID}}" --filter "name=ecs-worker" | head -1) /bin/bash -c "cd /home/jiminny && bash"root@73b64f5d54a3:/home/jiminny# php artisan automated-reports[2026-04-14 08:41:03] staging.INFO: [automated-reports] Started{"correlation_id": "c858e03f-62bd-462d-add2-c1e12a4c4cf8", "trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"'}[2026-04-14 08:41:03]staging.INFO:[automated-reports]Checking conditions {"isMonday":false,"isFirstDay0fMonth":false, "currentMonth":4, "isQuarterlyMonth":true} {"correlation_id": "c858e03f-62bd-462d-add2-c1e12a4c4cf8", "trace_id" : "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"}[2026-04-14 08:41:03] staging.INFO: [automated-reports] Processing daily reports6abfc67e02f"}{"correlation_id": "c858e03f-62bd-462d-add2-c1e12a4c4cf8", "trace_id": "94b4fdcc-f609-42e7-b5b7-b[2026-04-14 08:41:03Jstaging.INFO: [automated-reports] Found 3 dailyreports to process7-b5b7-b6abfc67e02f"}{"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8","trace_id":"94b4fdcc-f609-42e[2026-04-14 08:41:03Jstaging.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"fa7417aa-538e-49ab-8827-77235637a6f9","teamId" : 1, "frequency" : "daily", "type": "ask_jiminny"} {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"',"trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"}[2026-04-14 08:41:03]staging.INFO: [automated-reports]Dispatching Generate Report job for report {"reportUuid": "63e6d70b-b7cb-4dfa-8443-53453e6c4054", "teamId":1, "frequency" :"daily","type": "ask_jiminny"} {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"',"trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f''})[2026-04-14 08:41:04]staging.INFO: [automated-reports]Dispatching Generate Report job for report {"reportUuid":"7e7846e2-c0ea-4040-88f4-0ae14b66ade8","teamId" :1, "frequency" :"daily", "type": "ask_jiminny"} {"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8"',"trace_id": "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f"}[2026-04-14 08:41:04] staging.INFO: [automated-reports]Completed{"correlation_id":"c858e03f-62bd-462d-add2-c1e12a4c4cf8", "trace_id" : "94b4fdcc-f609-42e7-b5b7-b6abfc67e02f''}root@73b64f5d54a3:/home/jiminny#l...
|
NULL
|
|
9552
|
185
|
23
|
2026-04-14T07:43:56.964345+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776152636964_m2.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
a
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"a","depth":1,"value":"a","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
8186225505942432243
|
8186225505942432243
|
visual_change
|
hybrid
|
NULL
|
a
FirefoxFileEoitViewHistoryBookmarksProfilesTools a
FirefoxFileEoitViewHistoryBookmarksProfilesToolsWindowHelpiminny.atlassian.net/wiki/spaces/EN/pages/2112585768/Configure+SSH+access+to+multiple+environment~ Google GeminiJIMINNYPlatform Sprint 1 Q2 - Platform TeaEngineering/ E Configure SSH access to multiple environmentJY-20543 add AJ reports User piloSRD-6779 | JY-20632 | Unable to@ Jy 19798 evaluation for ai activity t(8 JiminnyAsk Jiminny test report - 8 Apr 20.Service-Desk - Queues - PlatformJY-20543 add AJ reports User pil• The Danger: While valid Python, injectingconditionals directly into a multi-linestring sequence via( "str" ifcondition else "" ) + f"str" ishighly prone to formatting bugs, missingspaces, or accidental syntax errors duringfuture refactors.• The Fix: Construct the prompt fragmentsusing standard control flow for betterreadability:Z Configure SSH access to multiPython+ New Takprompt_parts = ["# Answer\n\n"]if report mode:prompt_parts.append("**Important:prompt_parts. append(f"Synthesize all {Overly Defensive Call Count FallbackLine 195 contains: call_count =len(aa_request.call_ids) ifaa_request.call_ids else 0• The Danger: This impliesad_request.call_ids might be None.If an empty state is valid, this line handlesit correctly. However, a request to analyze"Ask Anything" on calls without anycall_ids represents a logically invalidrequest state.• The Fix: Ensure upstream validationcatches empty call_ids before itreaches the report generator, or explicitlyraise a ValueError here to preventgenerating a "Data Source" sectionbased on zero calls.Q Search across all your appsB 40 lbl l Support Daily- in 4h17m A 100% C/ & Tue 14 Apr 10:43:56+ CreateC AskRoVO A ® eUodaled Jan O4I Edit& Share@ ...d. ssh shared key to ecs instancesTo ssh into ecs instances you need a private key that is one per environment. We keep theshared keys in lpassword's Engineering VaultFor Stage:Look for ecs-stage in lpasswordCopy the ecs-stage.pem file in ~/ .ssh/jiminny/stage/ecs-stage.pemFor QA:Look for ecs-qa in lpasswordCopy the ecs-qa.pem file in ~/.ssh/jiminny/qa/ecs-qa.pemFor QAi:Look for ecs-qai in lpasswordCopy the ecs-qai.pem file in ~/.ssh/jiminny/qai/ecs-qai.pemFor Production US:Look for ecs-prod in lpasswordCopy the ecs.pem file in ~/.ssh/jiminny/production/ecs.pemFor Production EU:Look for ecs-eu in lpasswordCopy the ecs-eu.pem file in ~/.ssh/jiminny/production/ecs-eu.pemAfter copying the keys to your local system you need to fix their permissions:Change the permissions for the shared key to 600:1 chmod 600 ~/.ssh/jiminny/stage/ecs-stage.pem2 chmod 600 ~/.ssh/jiminny/qa/ecs-qa.pem3 chmod 600 ~/.ssh/jiminny/qai/ecs-qai.pemchmod 600 ~/.ssh/jiminny/production/ecs.pemchmod 600 ~/.ssh/jiminny/production/ecs-eu.pem• Enter a prompt for GeminiProvYour Jiminny chats aren't used to improve our models. Gemini is Alane can make mistakes, Inciualne aoout peodleYour privacy & GeminiSummarize page3. Add AWS profilesEdit ~/.aws/credentials to add the AWS profiles below:[stage]2 aws access key id = <YOUR AWS ACCESS KEY ID>aws_secret_access_key = <YOUR AWS SECRET ACCESS KEY>[default]role_arn = arn:awssource_protile = SMta serlal - arh.ah-yduratton seconds = 92444=. nly^V _ Highlight All Match Case Match Diacritics Whole Words 2 of 19 matches...
|
9550
|
|
13201
|
288
|
15
|
2026-04-14T12:14:42.953248+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776168882953_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
a
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"a","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"a","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
8186225505942432243
|
8186225505942432243
|
idle
|
hybrid
|
NULL
|
a
iTerm2ShellEditViewSessionScriptsProfilesWindowH a
iTerm2ShellEditViewSessionScriptsProfilesWindowHelp(ahlAPP (-zsh)DOCKER281DEV (docker)882APP (-zsh)83ec2-user@ip-10-30-…..₴84-zshdocker exec-itdocker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.php-v--using-cache=no --diffPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminskiandcontributors.PHPruntime:8.3.30Runninganalysison 7 cores with 10 files perParallel runneris an experimental feature arLoadedconfigdefault from-php-cs-fixer.dis5589/5589100Support Daily • 1 m left-zsh86-zsh100% <47O &7Tue 14 Apr 15:14:42181* Unable to acce... *- 88APPFixed 0 of 5589 files in 39.687 seconds, 67.6Activity Monitor.app/Applications/Utilities/Activity Monitor.appWhat's next:Try DockerDebug forseamless, persistentLearn moreat [URL_WITH_CREDENTIALS] exec -it docker_lamp_1/vendor/bin/ph,/Applications/Numbers.appPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminskiandcontributors.PHP runtime: 8.3.30Running analysis on 7 cores with 10 files per process.Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!Loadedconfig default from".php-cs-fixer.dist.php".5589/5589100%282883₴84*5H86$87888Fixed 0 of 5589 files in 49.458 seconds, 67.00 MB memory usedWhat's next:Try Docker Debug for seamless, persistentdebugging tools in any container or image » docker debug docker_lamp_1Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-18909-automated-reports-ask-jiminny) $D...
|
NULL
|
|
52927
|
1148
|
4
|
2026-04-20T07:49:44.492254+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776671384492_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
a
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"a","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.4763889,"height":0.05888889},"value":"a","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
8186225505942432243
|
8186225505942432243
|
visual_change
|
hybrid
|
NULL
|
a
FirefoxFileEditViewHistoryBookmarksProfilesTools a
FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp‹ >0 lhlDEV (docker)APP (-zsh)DOCKERLast login: Mon Apr 20 10:16:41 on ttys006DEV (docker)Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parentsPoetry could not find a pyproject.toml filelukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/J1root@docker_lamp_1:/home/jiminny# ]a*3-zshActivity Monitor.app/Applications/Utilities/Activity Monitor.appAWS serviceSequel Ace.app/Applications/Sequel Ace.appMusic.app/Applications/Music.appAnybox.app/Applications/Anybox.appArchive Utility.app/System/Library/CoreServices/Applications/Archive Utility.appKeychain Access.app/Applications/Utilities/Keychain Access.appAppFlowy.app/Applications/AppFlowy.appNumbers.app/Applications/Numbers.app282283₴84*5₴6$87888100% (8• *4screenpipe"Mon 20 Apr 10:49:44181*=*5DEV...
|
52926
|
|
13609
|
296
|
31
|
2026-04-14T12:35:32.842422+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776170132842_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
a
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"a","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.43194443,"height":0.05888889},"value":"a","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
8186225505942432243
|
8186225505942432243
|
visual_change
|
hybrid
|
NULL
|
a
SlackFileEditViewGoHistoryWindowHelp(MySQL 11.4. a
SlackFileEditViewGoHistoryWindowHelp(MySQL 11.4.9-MariaDB-log) PROD/jiminny/leads• leaTABLES_ lead_stagesleadsFieldiduuidteam_idcrm_conf...stage_idstage_up...record_ty.converte...converte...converte...converte...crm_prov...user_idowner_idcompanydomaincountry_..nametitleemailphoneext+INDEXESTypeINTBINARYaNon_unique11+@vLength• 1016activitiesaccountsINTVARCHARINTIVARCHARVARCHARVARCHARCHARVARCHARVARCHARVARCHARVARCHARCHAR* 10128101281911912191128802510Key_namePRIMARYleads_u...leads_c...leads_c...leads_t...leads_S...leads_c...leads_c...leads_c...leads_r...leads_u...ІаSнC nSeq_in_ind...Column_nameiduuidcrm_config...crm_provid...team_idstage_idconverted_...converted_...converted_...record_typ...user_idArm canfinCollationCardin87568875688387568686977960758379125092292Q1EDHomeFilesLater.*•More(ab)Retro - Platform • in 1h 25 m100% <47Tue 14 Apr 15:35:32Search Jiminny IncJiminny ...+tscnicrel+ InLUA AhARAAd₴2# support# thank-yous# the_people_of jimi...Direct messages3Aneliya Angelo...€. Vasil Vasilev Elo Steliyan GeorgievAdelina Petrova, Ili...0. Adelina Petrova. Galya Dimitrova *0g Nikolay Nikolov "Y2Galya Dimitrova, Ni...2Galya Dimitrova, Ni...P. Nikolay Yankov::: AppsToastThread Direct message with 3 othersLukas Kovalik 1 hour agoза второто е направено да праща веднагасамо при one-off, иначе си праща когато мудойде време през нощтаи тука мога да му да добавя параметьр самоза тестване и да прати веднага ако се подадена командаNikolay Yankov 45 minutes agoда, да направим за да го тестваме, иначе нямада можемLukas Kovalik 5 minutes agoготовОphp artisan automated-reports:send --result-id {RESULT ID}след като е генериран може да се пуска такаNikolay Yankov 4 minutes agoдобре, но аз не сьм логнат там, можеш ли даго пуснешпуснах нов репорт test 7Lukas Kovalik 2 minutes agoмога ако ми дадеш result idне ти ли работи инстанция?Nikolay Yankov 1 minute ago18a06a75-afd2-476f-aadc-14d4057bdda2Reply...Jira CloudGoogle Cale...Also send to the group+Aa•*•...
|
NULL
|
|
41481
|
879
|
39
|
2026-04-17T06:14:40.134396+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-17/1776 /Users/lukas/.screenpipe/data/data/2026-04-17/1776406480134_m1.jpg...
|
Alfred
|
Alfred
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
a
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"a","depth":1,"bounds":{"left":0.26180556,"top":0.16777778,"width":0.43194443,"height":0.05888889},"value":"a","help_text":"Alfred Search","role_description":"text field","is_enabled":true,"is_focused":true}]...
|
8186225505942432243
|
8186225505942432243
|
visual_change
|
hybrid
|
NULL
|
a
PhpStormFileEditViewNavigateCodeLaravelRefactorR a
PhpStormFileEditViewNavigateCodeLaravelRefactorRunToolsGitWindowHelpEU (ssh)DOCKERDEV (-zsh)O $2APP (-zsh)883-zshDOCKER (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/infrastructure/dev/docker or its parentsPoetry could notfind a pyproject.tomlfile/docker or its parents‹ →0 llA100% <Fri 17 Apr 9:14:39L88184-zsh®• ₴5PROD (ssh)Run'do-release-upgrade' to upgrade to it.* Review screenpipe U...•*6-zshPROD2.39.71.189Ulukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jіüjilBuild mysql uuid queryChoose table name$Lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/intrastructure/dev/aocker (aevelop)$ 0*** System restart required ***Last login: Thu Apr 16 06:55:03 2026 from 212.39.71.189lukas@jiminny-eu-bastion:~$T4 STAGE (-zsh)Last login: Thu Apr 16 15:43:43 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny$T5 QA (-zsh)Last login: Thu Apr 16 15:43:43 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsXT6 FE (-zsh)Last login: Thu Apr 16 15:48:07 on ttys004Poetry could not find a pyproject.toml file in /Users/lukas or its parents RONTENDPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ IX T7 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|-...
|
NULL
|
|
72881
|
1779
|
16
|
2026-04-23T06:21:08.860540+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776925268860_m2.jpg...
|
PhpStorm
|
faVsco.js – UserTransformer.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
"Reposit" not found, press ⌘G to search fr "Reposit" not found, press ⌘G to search from the top
text/html
text/html
text/html
Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
Reposit
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
10/10
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
13
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Transformers;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\Sidekick\SidekickService;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Controllers\Settings\Users\Utils\UserSetting;
use Jiminny\Models\Activity\Provider;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\JobTitle;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\UserRepository;
use Jiminny\Services\Notification\Messengers\MsTeams;
use Jiminny\Services\UserService;
use League\Fractal\Resource;
use League\Fractal\Resource\Item;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
protected array $availableIncludes = [
'team',
'group',
'job',
'roles',
'permissions',
];
private Container $container;
private bool $withSelfVisibility = false;
public function __construct(?Container $container = null)
{
$this->container = $container ?? app();
}
public function withSelfVisibility(): self
{
$this->withSelfVisibility = true;
return $this;
}
/**
* @throws ActivityProviderException
*
* @return array<string, mixed>
*/
public function transform(User $user): array
{
$attributes = [
'id' => $user->getUuid(),
'name' => $user->getName(),
'firstName' => $user->getFirstName(),
'photoUrl' => $user->getPhotoUrl(),
'conferenceRecordPreference' => $user->checkConferenceRecordPreference(),
'conferenceRecordInternalPreference' => $user->checkConferenceRecordInternalPreference(),
// DO NOT USE User::isCrmRequired as it is not hydrated when fetched from ES!
'crmRequired' => $user->crm_required,
'slackFollowUp' => $user->slack_follow_up,
];
// DO NOT USE User::getId as it is not hydrated when fetched from ES!
if ($this->withSelfVisibility || (auth()->check() && auth()->user()->id === $user->id)) {
$softphoneHasVoiceCapability = $user->hasSoftphoneNumberCapabilities()
&& $user->getSoftphoneNumberCapabilities()->hasVoiceCapability()
;
$conferenceSidekickOpen = $user->getConferenceSidekickOpen();
$softphoneSidekickOpen = $user->getSoftphoneSidekickOpen();
$conferenceSidekickPopupOverridden = false;
$softphoneSidekickPopupOverridden = false;
$hasSidekickEnabled = true;
if ($user->getTeam()->hasFeature(FeatureEnum::SIDEKICK_SETTINGS)) {
$sidekickService = $this->getSidekickService();
$sidekickData = $sidekickService->getSidekickSettingsForUser($user);
$conferenceSidekickOpen = $sidekickData['conferenceSettings'];
$softphoneSidekickOpen = $sidekickData['softphoneSettings'];
$conferenceSidekickPopupOverridden = $sidekickData['conferenceSidekickPopupOverridden'];
$softphoneSidekickPopupOverridden = $sidekickData['softphoneSidekickPopupOverridden'];
$hasSidekickEnabled = $sidekickData['sidekickEnabled'];
}
$userService = $this->getUserService();
$dealInsightsPeriod = $userService->getDealInsightTimelinePeriod($user);
$dataFormatCountryCode = $userService->getDateTimeCountryCode($user);
// Attributes for the user only.
$attributes += [
'conferenceJoinReminder' => $user->conference_join_reminder,
'softphoneInboundRecordPreference' => $user->checkSoftphoneInboundRecordPreference(),
'softphoneOutboundRecordPreference' => $user->checkSoftphoneOutboundRecordPreference(),
'softphoneInboundDestination' => $user->softphone_inbound_destination,
'softphoneHasVoiceCapability' => $softphoneHasVoiceCapability,
'softphoneNumber' => $user->getSoftPhoneNumber(),
'formattedSoftphoneNumber' => $user->getFormattedSoftphoneNumberAttribute(),
'email' => $user->getEmailAddress(),
'secondaryEmail' => $user->getSecondaryEmailAddress(),
'phone' => $user->phone,
'secondaryPhone' => $user->secondary_phone,
'callerId' => $user->getCallerId(),
'countryCode' => $user->getCountryCode(),
'timezone' => $user->getTimezone()->getName(),
'language' => $user->getLanguage(),
'locales' => $this->container->get(UserRepository::class)->getUserLocales($user),
'status' => $user->getStatus(),
'hash' => $user->generateHash(),
'registrationDate' => $user->created_at ? $user->created_at->toIso8601String() : null,
'notifyLiveCoaching' => $user->notify_live_coaching,
'activityLogReminder' => $user->activity_log_reminder,
'conferenceSidekickOpen' => $conferenceSidekickOpen,
'softphoneSidekickOpen' => $softphoneSidekickOpen,
'conferenceSidekickPopupOverridden' => $conferenceSidekickPopupOverridden,
'softphoneSidekickPopupOverridden' => $softphoneSidekickPopupOverridden,
'hasSidekickEnabled' => $hasSidekickEnabled,
'activityActionItems' => $user->activity_action_items,
'syncEmail' => $user->isSyncEmailEnabled(),
'syncConference' => $user->sync_conference,
'syncDialer' => $user->shouldSyncDialer(),
'needsToConfigurePhoneNumber' => $this->container
->get('onboarding_phone_decider')
->isOnboardable($user),
'shouldShowPhoneNumberField' => $this->container
->get('onboarding_phone_decider')
->shouldShowPhoneNumberField($user),
UserSetting::DEAL_INSIGHTS_TIMELINE_PERIOD => $dealInsightsPeriod,
'countryByTimezone' => $dataFormatCountryCode,
'conferenceSlug' => $user->getConferenceSlug(),
'conferenceRecordExternalOrganizerPreference' =>
$userService->isConferenceRecordExternalOrganizerPreferenceEnabled($user),
'hasGeneratedAiReports' => $this->getAutomatedReportsRepository()->countUserReports($user) > 0,
'sendEmailWhenExportLinkIsOpened' => $userService->canSendEmailWhenExportLinkIsOpened($user),
];
if ($user->softphone_debug) {
$attributes += [
'debugSoftphone' => $user->softphone_debug, // Needed?
];
}
}
if ($user->getTeam()->getNotificationProvider() === Team::NOTIFICATION_PROVIDER_MSTEAMS) {
$socialAccountMS = $user->getSocialAccount(Team::CALENDAR_PROVIDER_OFFICE);
$state = $socialAccountMS !== null
? str_contains($socialAccountMS->auth_scope, MsTeams::SCOPE_CHAT_CREATE)
: null;
$attributes['integrations']['office'] = [
'displayName' => 'Microsoft Teams',
'apiName' => 'microsoft-teams',
'types' => ['notification'],
'state' => $state ? Provider::STATE_INSTALLED : Provider::STATE_NOT_INSTALLED,
'logo' => cdn('img/ms-teams-logo.svg'),
'installationStrategy' => 'oauth',
];
}
return $attributes;
}
public function includeTeam(User $user): Item
{
$team = $user->getTeam();
return $this->item($team, $this->getTeamTransformer());
}
public function includeGroup(User $user): ?Item
{
$group = $user->getGroup();
if ($group === null) {
return null;
}
return $this->item($group, $this->getGroupTransformer());
}
public function includeJob(User $user): ?Item
{
$job = $user->getJobTitle();
if (! $job instanceof JobTitle) {
return null;
}
return $this->item($job, $this->getJobTitleTransformer());
}
public function includeRoles(User $user): Resource\Collection
{
/** @var Collection<int, string> $roles */
$roles = $user->roles()
->where('is_visible', true)
->pluck('name')
->toArray();
return $this->collection($roles, $this->getRoleTransformer());
}
public function includePermissions(User $user): Resource\Collection
{
$permissions = $user->allPermissions();
return $this->collection($permissions, $this->getPermissionTransformer());
}
public function includeIntegrations(User $user): Item
{
return $this->item($user, $this->getIntegrationsTransformer());
}
private function getTeamTransformer(): TransformerAbstract
{
return $this->container->get(TeamTransformer::class);
}
private function getGroupTransformer(): GroupTransformer
{
return $this->container->get(GroupTransformer::class);
}
private function getIntegrationsTransformer(): IntegrationTransformer
{
return $this->container->get(IntegrationTransformer::class);
}
private function getPermissionTransformer(): PermissionTransformer
{
return $this->container->get(PermissionTransformer::class);
}
private function getRoleTransformer(): RoleTransformer
{
return $this->container->get(RoleTransformer::class);
}
private function getJobTitleTransformer(): JobTitleTransformer
{
return $this->container->get(JobTitleTransformer::class);
}
private function getSidekickService(): SidekickService
{
/** @var SidekickService */
return $this->container->get(SidekickService::class);
}
private function getUserService(): UserService
{
/** @var UserService */
return $this->container->get(UserService::class);
}
private function getAutomatedReportsRepository(): AutomatedReportsRepository
{
/** @var AutomatedReportsRepository */
return $this->container->get(AutomatedReportsRepository::class);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
36
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Component\DealInsights;
use Doctrine\DBAL\Connection;
use Generator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Jiminny\Component\DealInsights\Forecast\DealData;
use Jiminny\Component\DealInsights\Forecast\DealsFilter;
use Jiminny\Component\DealInsights\QueryBuilder\QueryBuilder;
use Jiminny\Component\DealInsights\QueryBuilder\Visitor\QueryBuilderVisitorInterface;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Stage;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Services\Crm\IntegrationApp\DTO\Utils\UrlGeneratorInterface;
use Jiminny\Services\Crm\ProviderRegistry;
use Jiminny\Traits\RequiresUUID;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Eloquent;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class DealsRepository implements DealsRepositoryInterface
{
private Connection $connection;
private ProviderRegistry $providerRegistry;
/**
* @var QueryBuilderVisitorInterface[]
*/
private array $visitors = [];
/**
* @param QueryBuilderVisitorInterface[] $visitors
*/
public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])
{
$this->connection = $connection;
$this->providerRegistry = $crmProviderRegistry;
foreach ($visitors as $visitor) {
$this->visitors[$visitor->getIdentifier()] = $visitor;
}
}
public function getDeals(CriteriaInterface $criteria): array
{
$context = $criteria->getContext();
$team = $context->getTeam();
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$this->visit($qb, $criteria);
return $this->execute($team, $crmService, $qb);
}
public function getDeal(Team $team, int $id): array
{
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$qb->andWhere('opp.id = :id')->setParameter('id', $id);
return $this->execute($team, $crmService, $qb);
}
public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])
{
$qb = new QueryBuilder($this->connection);
$qb
->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')
->from('crm_fields', 'f')
->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')
->where('f.crm_configuration_id = :crm')
->andWhere('f.object_type = :type')
->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')
->orderBy('fd.object_id', 'ASC')
->addOrderBy('fd.updated_at', 'ASC')
->setParameter('type', Field::OBJECT_OPPORTUNITY)
->setParameter('crm', $crmId)
;
if (! empty($crmFields)) {
$fields = array_map(fn ($value): string => '"' . $value . '"', $crmFields);
$qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');
}
return $qb->executeQuery()->fetchAllAssociative();
}
public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAssociative();
}
public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('COALESCE(opp.currency_code, "' . $defaultCurrency . '") AS currency')
->addSelect('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
->groupBy('currency')
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAllAssociative();
}
public function getDealActivities(CriteriaInterface $criteria): array
{
$qb = Activity::with(['participants', 'user'])
->where('opportunity_id', $criteria->getOpportunityId())
->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())
->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())
->orderBy($criteria->getSortBy(), $criteria->getSortDirection())
;
// Should we filter activities by criteria? It's intended to filter deals.
return $qb->get()->all();
}
public function getStages(CriteriaInterface $criteria): array
{
$qb = new QueryBuilder($this->connection);
$qb
->select('id', 'label', 'sequence')
->from('stages', 's')
->where('crm_configuration_id = :crm_configuration_id')
->andWhere('type = :type')
->orderBy('sequence', 'ASC')
->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())
->setParameter('type', Stage::TYPE_OPPORTUNITY);
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$result[$row['id']] = [
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
public function getConfigurationStages(Configuration $configuration): Collection
{
return $configuration
->stages()
->where('type', Stage::TYPE_OPPORTUNITY)
->get();
}
public function getPipelineData(Configuration $crm): array
{
$qb = new QueryBuilder($this->connection);
$provider = $crm->provider;
$qb
->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')
->from('stages', 's')
->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')
->where('s.crm_configuration_id = :crm_configuration_id')
->andWhere('s.type = :type')
->orderBy('bps.business_process_id', 'ASC')
->addOrderBy('s.sequence', 'ASC')
->setParameter('crm_configuration_id', $crm->id)
->setParameter('type', Stage::TYPE_OPPORTUNITY)
;
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];
$result[$row['pipeline_id']][] = [
'value' => $value,
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
private function createQueryBuilder(string $realm): QueryBuilder
{
return (new QueryBuilder($this->connection))
->setRealm($realm)
->from('opportunities', 'opp')
->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')
->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')
->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')
;
}
/**
* Applies all applicable visitors and returns the IDs of the executed ones
*
* @return string[]
*/
private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array
{
$queryVisitors = [];
foreach ($this->visitors as $visitor) {
if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {
$visitor->visit($queryBuilder, $criteria);
$queryVisitors[] = $visitor->getIdentifier();
}
}
return $queryVisitors;
}
private function hydrateStages(array $deals): array
{
foreach ($this->fetchStages(array_keys($deals)) as $stage) {
$oppId = (int) $stage['opportunity_id'];
if (! isset($deals[$oppId])) {
continue; // or throw??!
}
$deals[$oppId]['stages'][] = [
'id' => $stage['stage_id'],
'name' => $stage['label'],
'enteredAt' => $stage['created_at'],
];
}
return $deals;
}
/**
* @param int[] $dealIds
*/
private function fetchStages(array $dealIds): array
{
if (empty($dealIds)) {
return [];
}
$qb = new QueryBuilder($this->connection);
$qb
->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')
->from('opportunity_stages', 'os')
->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')
->where($qb->expr()->in('os.opportunity_id', $dealIds))
->orderBy('os.opportunity_id', 'ASC')
->addOrderBy('s.created_at', 'ASC')
;
return $qb->executeQuery()->fetchAllAssociative();
}
private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array
{
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$data = [
'uuid' => RequiresUUID::toNormal($row['uuid']),
'name' => $row['name'],
'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),
'account' => [
'name' => $row['acc_name'],
'url' => $crmService->generateProviderUrl(
providerId: $row['acc_provider_id'],
objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'
),
],
'owner' => null,
'rawValue' => [
'amount' => (float) $row['value'],
'currency' => $row['currency_code'],
],
'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),
'openDate' => $row['remotely_created_at'] ?? null,
'closeDate' => $row['close_date'] ?? null,
'stages' => [],
'currentPipelineId' => $row['pipeline_id'],
'currentStage' => [
'id' => $row['stage_id'],
'enteredAt' => $row['stage_updated_at'],
],
'currentStageUpdatedAt' => $row['stage_updated_at'],
'isClosed' => (bool) $row['is_closed'],
'isWon' => (bool) $row['is_won'],
];
if (isset($row['owner_uuid'])) {
$data['owner'] = [
'uuid' => RequiresUUID::toNormal($row['owner_uuid']),
'name' => $row['owner_name'],
'photoUrl' => $row['owner_photo'] === null
? null
: client_cdn($row['owner_photo'], $team),
'id' => $row['owner_id'],
'job' => $row['owner_job'],
];
}
$result[(int) $row['opp_id']] = $data;
}
return $this->hydrateStages($result);
}
private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder
{
$qb = clone $queryBuilder;
$qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');
$qb
->select(...[
'opp.id as opp_id',
'opp.uuid',
'opp.name',
'opp.value',
'opp.currency_code',
'opp.close_date',
'opp.remotely_created_at',
'opp.is_closed',
'opp.is_won',
])
->addSelect(...[
'usr.uuid as owner_uuid',
'usr.name AS owner_name',
'usr.photo_path as owner_photo',
'usr.id AS owner_id',
'jt.name as owner_job',
])
->addSelect('opp.stage_id', 'opp.stage_updated_at')
->addSelect(...[
'acc.name AS acc_name',
'acc.is_internal as acc_is_internal',
'opp.stage_updated_at',
'acc.crm_provider_id AS acc_provider_id',
'opp.crm_provider_id AS opp_provider_id',
])
->addSelect('rt.business_process_id AS pipeline_id')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'));
return $qb;
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws SocialAccountTokenInvalidException
*/
private function getCrmService(Team $team): ServiceInterface
{
$crmService = $this->providerRegistry->get($team->crm->provider);
$crmService->setConfiguration($team->crm);
if ($crmService instanceof UrlGeneratorInterface) {
$crmService->setCrmUrlGenerator($team->crm);
}
return $crmService;
}
/**
*
* @return Generator<DealData>
*/
public function getForecastData(DealsFilter $filter): Generator
{
$opportunities = DB::query()
->select([
'o.value',
'o.close_date',
'o.currency_code',
'o.is_won',
'o.is_closed',
'o.probability',
'o.forecast_category',
])
->from('opportunities', 'o')
->join('users', 'users.id', '=', 'o.user_id')
->join('groups', 'groups.id', '=', 'users.group_id')
->where('users.team_id', $filter->getTeam()->getId())
->where('o.close_date', '>=', $filter->getStartDate())
->where('o.close_date', '<=', $filter->getEndDate())
->where('o.currency_code', $filter->getCurrency())
->where('o.deleted_at', '=', null)
;
$userUuidList = $filter->getUserUuidList();
if (! empty($userUuidList)) {
$userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);
$opportunities->whereIn('users.uuid', $userUuidList);
}
$groupUuidList = $filter->getGroupUuidList();
if (! empty($groupUuidList)) {
$groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);
$opportunities->whereIn('groups.uuid', $groupUuidList);
}
foreach ($opportunities->cursor() as $row) {
yield new DealData(
(float) $row->value,
$row->close_date,
! empty($row->is_won),
! empty($row->is_closed),
$row->probability ?: 0,
$row->forecast_category ?: '',
);
}
}
public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection
{
return $user->subscriptionSets()
->where(static function (Eloquent\Builder $query): void {
$query
->whereNull('expired_at')
->orWhere('expired_at', '>=', now());
})
->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {
$join
->on('subscription_set_id', '=', 'activity_subscription_sets.id');
$join
->where('followable_type', Models\Activity\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)
->whereIn('followable_id', $opportunityIds);
})
->pluck('followable_id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType
AiAutomation
AiCallScoring
AskAnything
Dtos
Events
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country
CustomerApi
Database
Datadog
DateTime
DealInsights
Activity
ActivityAggregator.php, class
ActivityAggregatorInterface.php, interface
DatabaseActivities.php, class
DatasourceInterface.php, interface
RelatedActivity.php, class
RelatedActivityInterface.php, interface
Commands
Comments
Forecast
Jobs
QueryBuilder
Services
ClosingPeriodOptionDecorator.php, class
CreatedPeriodOptionDecorator.php, class
Criteria.php, class
CriteriaInterface.php, interface
CriteriaNormalizer.php, class
CrmService.php, class
CrmServiceInterface.php, interface
DealContactService.php, class
DealInsightsCriteriaBuilder.php, class
DealService.php, class
DealServiceInterface.php, interface
DealsRepository.php, class
DealsRepositoryInterface.php, interface
DealsServiceRepositories.php, class
PerformanceMonitor.php, class
PeriodOptionDecoratorInterface.php, interface
PeriodService.php, final class
PeriodServiceInterface.php, interface
DealRisks
DealRiskTypes
DealRisk.php, class
DealRisksRepository.php, class
DealRisksService.php, class
DealRisksServiceInterface.php, interface
DealRiskType.php
GroupDealRiskType.php
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection
LiveFeed
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue, folder
Router, folder
Saml2, folder
SCIM, folder
Seeder, folder
Sentry, folder
Serializer, folder
Settings, folder
Sidekick, folder
Slack, folder
TeamInsights, folder
TimeMemoryMapper, folder
Transcription, folder
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"\"Reposit\" not found, press ⌘G to search from the top","depth":2,"bounds":{"left":0.52293885,"top":0.8739026,"width":0.109042555,"height":0.013567438},"value":"\"Reposit\" not found, press ⌘G to search from the top","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"bounds":{"left":0.52293885,"top":0.8739026,"width":0.109042555,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.25731382,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20157-AJ-report-not-send-notification, menu","depth":5,"bounds":{"left":0.29587767,"top":0.019952115,"width":0.10139628,"height":0.025538707},"help_text":"Git Branch: JY-20157-AJ-report-not-send-notification","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.7972075,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"bounds":{"left":0.8125,"top":0.019952115,"width":0.10305851,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.35239363,"top":0.15482841,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.3650266,"top":0.15403032,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"Reposit","depth":4,"bounds":{"left":0.37599733,"top":0.15403032,"width":0.051861703,"height":0.015961692},"value":"Reposit","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.4368351,"top":0.15403032,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.44680852,"top":0.15403032,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.4554521,"top":0.15403032,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.46409574,"top":0.15403032,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10/10","depth":4,"bounds":{"left":0.47772607,"top":0.15323225,"width":0.025598405,"height":0.017557861},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.50332445,"top":0.15243416,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.5119681,"top":0.15243416,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.5206117,"top":0.15243416,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.52925533,"top":0.15243416,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.6918218,"top":0.15243416,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"13","depth":4,"bounds":{"left":0.66921544,"top":0.18355946,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.68085104,"top":0.18355946,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.69049203,"top":0.1819633,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6978058,"top":0.1819633,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Http\\Transformers;\n\nuse Illuminate\\Contracts\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\Sidekick\\SidekickService;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Controllers\\Settings\\Users\\Utils\\UserSetting;\nuse Jiminny\\Models\\Activity\\Provider;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\JobTitle;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\UserRepository;\nuse Jiminny\\Services\\Notification\\Messengers\\MsTeams;\nuse Jiminny\\Services\\UserService;\nuse League\\Fractal\\Resource;\nuse League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\TransformerAbstract;\n\nclass UserTransformer extends TransformerAbstract\n{\n protected array $availableIncludes = [\n 'team',\n 'group',\n 'job',\n 'roles',\n 'permissions',\n ];\n\n private Container $container;\n\n private bool $withSelfVisibility = false;\n\n public function __construct(?Container $container = null)\n {\n $this->container = $container ?? app();\n }\n\n public function withSelfVisibility(): self\n {\n $this->withSelfVisibility = true;\n\n return $this;\n }\n\n /**\n * @throws ActivityProviderException\n *\n * @return array<string, mixed>\n */\n public function transform(User $user): array\n {\n $attributes = [\n 'id' => $user->getUuid(),\n 'name' => $user->getName(),\n 'firstName' => $user->getFirstName(),\n 'photoUrl' => $user->getPhotoUrl(),\n 'conferenceRecordPreference' => $user->checkConferenceRecordPreference(),\n 'conferenceRecordInternalPreference' => $user->checkConferenceRecordInternalPreference(),\n // DO NOT USE User::isCrmRequired as it is not hydrated when fetched from ES!\n 'crmRequired' => $user->crm_required,\n 'slackFollowUp' => $user->slack_follow_up,\n ];\n\n // DO NOT USE User::getId as it is not hydrated when fetched from ES!\n if ($this->withSelfVisibility || (auth()->check() && auth()->user()->id === $user->id)) {\n $softphoneHasVoiceCapability = $user->hasSoftphoneNumberCapabilities()\n && $user->getSoftphoneNumberCapabilities()->hasVoiceCapability()\n ;\n\n $conferenceSidekickOpen = $user->getConferenceSidekickOpen();\n $softphoneSidekickOpen = $user->getSoftphoneSidekickOpen();\n $conferenceSidekickPopupOverridden = false;\n $softphoneSidekickPopupOverridden = false;\n $hasSidekickEnabled = true;\n\n if ($user->getTeam()->hasFeature(FeatureEnum::SIDEKICK_SETTINGS)) {\n $sidekickService = $this->getSidekickService();\n\n $sidekickData = $sidekickService->getSidekickSettingsForUser($user);\n\n $conferenceSidekickOpen = $sidekickData['conferenceSettings'];\n $softphoneSidekickOpen = $sidekickData['softphoneSettings'];\n $conferenceSidekickPopupOverridden = $sidekickData['conferenceSidekickPopupOverridden'];\n $softphoneSidekickPopupOverridden = $sidekickData['softphoneSidekickPopupOverridden'];\n $hasSidekickEnabled = $sidekickData['sidekickEnabled'];\n }\n\n $userService = $this->getUserService();\n $dealInsightsPeriod = $userService->getDealInsightTimelinePeriod($user);\n $dataFormatCountryCode = $userService->getDateTimeCountryCode($user);\n\n // Attributes for the user only.\n $attributes += [\n 'conferenceJoinReminder' => $user->conference_join_reminder,\n 'softphoneInboundRecordPreference' => $user->checkSoftphoneInboundRecordPreference(),\n 'softphoneOutboundRecordPreference' => $user->checkSoftphoneOutboundRecordPreference(),\n 'softphoneInboundDestination' => $user->softphone_inbound_destination,\n 'softphoneHasVoiceCapability' => $softphoneHasVoiceCapability,\n 'softphoneNumber' => $user->getSoftPhoneNumber(),\n 'formattedSoftphoneNumber' => $user->getFormattedSoftphoneNumberAttribute(),\n 'email' => $user->getEmailAddress(),\n 'secondaryEmail' => $user->getSecondaryEmailAddress(),\n 'phone' => $user->phone,\n 'secondaryPhone' => $user->secondary_phone,\n 'callerId' => $user->getCallerId(),\n 'countryCode' => $user->getCountryCode(),\n 'timezone' => $user->getTimezone()->getName(),\n 'language' => $user->getLanguage(),\n 'locales' => $this->container->get(UserRepository::class)->getUserLocales($user),\n 'status' => $user->getStatus(),\n 'hash' => $user->generateHash(),\n 'registrationDate' => $user->created_at ? $user->created_at->toIso8601String() : null,\n 'notifyLiveCoaching' => $user->notify_live_coaching,\n 'activityLogReminder' => $user->activity_log_reminder,\n 'conferenceSidekickOpen' => $conferenceSidekickOpen,\n 'softphoneSidekickOpen' => $softphoneSidekickOpen,\n 'conferenceSidekickPopupOverridden' => $conferenceSidekickPopupOverridden,\n 'softphoneSidekickPopupOverridden' => $softphoneSidekickPopupOverridden,\n 'hasSidekickEnabled' => $hasSidekickEnabled,\n 'activityActionItems' => $user->activity_action_items,\n 'syncEmail' => $user->isSyncEmailEnabled(),\n 'syncConference' => $user->sync_conference,\n 'syncDialer' => $user->shouldSyncDialer(),\n 'needsToConfigurePhoneNumber' => $this->container\n ->get('onboarding_phone_decider')\n ->isOnboardable($user),\n 'shouldShowPhoneNumberField' => $this->container\n ->get('onboarding_phone_decider')\n ->shouldShowPhoneNumberField($user),\n UserSetting::DEAL_INSIGHTS_TIMELINE_PERIOD => $dealInsightsPeriod,\n 'countryByTimezone' => $dataFormatCountryCode,\n 'conferenceSlug' => $user->getConferenceSlug(),\n 'conferenceRecordExternalOrganizerPreference' =>\n $userService->isConferenceRecordExternalOrganizerPreferenceEnabled($user),\n 'hasGeneratedAiReports' => $this->getAutomatedReportsRepository()->countUserReports($user) > 0,\n 'sendEmailWhenExportLinkIsOpened' => $userService->canSendEmailWhenExportLinkIsOpened($user),\n ];\n\n if ($user->softphone_debug) {\n $attributes += [\n 'debugSoftphone' => $user->softphone_debug, // Needed?\n ];\n }\n }\n\n if ($user->getTeam()->getNotificationProvider() === Team::NOTIFICATION_PROVIDER_MSTEAMS) {\n $socialAccountMS = $user->getSocialAccount(Team::CALENDAR_PROVIDER_OFFICE);\n $state = $socialAccountMS !== null\n ? str_contains($socialAccountMS->auth_scope, MsTeams::SCOPE_CHAT_CREATE)\n : null;\n\n $attributes['integrations']['office'] = [\n 'displayName' => 'Microsoft Teams',\n 'apiName' => 'microsoft-teams',\n 'types' => ['notification'],\n 'state' => $state ? Provider::STATE_INSTALLED : Provider::STATE_NOT_INSTALLED,\n 'logo' => cdn('img/ms-teams-logo.svg'),\n 'installationStrategy' => 'oauth',\n ];\n }\n\n return $attributes;\n }\n\n public function includeTeam(User $user): Item\n {\n $team = $user->getTeam();\n\n return $this->item($team, $this->getTeamTransformer());\n }\n\n public function includeGroup(User $user): ?Item\n {\n $group = $user->getGroup();\n if ($group === null) {\n return null;\n }\n\n return $this->item($group, $this->getGroupTransformer());\n }\n\n public function includeJob(User $user): ?Item\n {\n $job = $user->getJobTitle();\n\n if (! $job instanceof JobTitle) {\n return null;\n }\n\n return $this->item($job, $this->getJobTitleTransformer());\n }\n\n public function includeRoles(User $user): Resource\\Collection\n {\n /** @var Collection<int, string> $roles */\n $roles = $user->roles()\n ->where('is_visible', true)\n ->pluck('name')\n ->toArray();\n\n return $this->collection($roles, $this->getRoleTransformer());\n }\n\n public function includePermissions(User $user): Resource\\Collection\n {\n $permissions = $user->allPermissions();\n\n return $this->collection($permissions, $this->getPermissionTransformer());\n }\n\n public function includeIntegrations(User $user): Item\n {\n return $this->item($user, $this->getIntegrationsTransformer());\n }\n\n private function getTeamTransformer(): TransformerAbstract\n {\n return $this->container->get(TeamTransformer::class);\n }\n\n private function getGroupTransformer(): GroupTransformer\n {\n return $this->container->get(GroupTransformer::class);\n }\n\n private function getIntegrationsTransformer(): IntegrationTransformer\n {\n return $this->container->get(IntegrationTransformer::class);\n }\n\n private function getPermissionTransformer(): PermissionTransformer\n {\n return $this->container->get(PermissionTransformer::class);\n }\n\n private function getRoleTransformer(): RoleTransformer\n {\n return $this->container->get(RoleTransformer::class);\n }\n\n private function getJobTitleTransformer(): JobTitleTransformer\n {\n return $this->container->get(JobTitleTransformer::class);\n }\n\n private function getSidekickService(): SidekickService\n {\n /** @var SidekickService */\n return $this->container->get(SidekickService::class);\n }\n\n private function getUserService(): UserService\n {\n /** @var UserService */\n return $this->container->get(UserService::class);\n }\n\n private function getAutomatedReportsRepository(): AutomatedReportsRepository\n {\n /** @var AutomatedReportsRepository */\n return $this->container->get(AutomatedReportsRepository::class);\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Http\\Transformers;\n\nuse Illuminate\\Contracts\\Container\\Container;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Component\\Sidekick\\SidekickService;\nuse Jiminny\\Exceptions\\ActivityProviderException;\nuse Jiminny\\Http\\Controllers\\Settings\\Users\\Utils\\UserSetting;\nuse Jiminny\\Models\\Activity\\Provider;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\JobTitle;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\UserRepository;\nuse Jiminny\\Services\\Notification\\Messengers\\MsTeams;\nuse Jiminny\\Services\\UserService;\nuse League\\Fractal\\Resource;\nuse League\\Fractal\\Resource\\Item;\nuse League\\Fractal\\TransformerAbstract;\n\nclass UserTransformer extends TransformerAbstract\n{\n protected array $availableIncludes = [\n 'team',\n 'group',\n 'job',\n 'roles',\n 'permissions',\n ];\n\n private Container $container;\n\n private bool $withSelfVisibility = false;\n\n public function __construct(?Container $container = null)\n {\n $this->container = $container ?? app();\n }\n\n public function withSelfVisibility(): self\n {\n $this->withSelfVisibility = true;\n\n return $this;\n }\n\n /**\n * @throws ActivityProviderException\n *\n * @return array<string, mixed>\n */\n public function transform(User $user): array\n {\n $attributes = [\n 'id' => $user->getUuid(),\n 'name' => $user->getName(),\n 'firstName' => $user->getFirstName(),\n 'photoUrl' => $user->getPhotoUrl(),\n 'conferenceRecordPreference' => $user->checkConferenceRecordPreference(),\n 'conferenceRecordInternalPreference' => $user->checkConferenceRecordInternalPreference(),\n // DO NOT USE User::isCrmRequired as it is not hydrated when fetched from ES!\n 'crmRequired' => $user->crm_required,\n 'slackFollowUp' => $user->slack_follow_up,\n ];\n\n // DO NOT USE User::getId as it is not hydrated when fetched from ES!\n if ($this->withSelfVisibility || (auth()->check() && auth()->user()->id === $user->id)) {\n $softphoneHasVoiceCapability = $user->hasSoftphoneNumberCapabilities()\n && $user->getSoftphoneNumberCapabilities()->hasVoiceCapability()\n ;\n\n $conferenceSidekickOpen = $user->getConferenceSidekickOpen();\n $softphoneSidekickOpen = $user->getSoftphoneSidekickOpen();\n $conferenceSidekickPopupOverridden = false;\n $softphoneSidekickPopupOverridden = false;\n $hasSidekickEnabled = true;\n\n if ($user->getTeam()->hasFeature(FeatureEnum::SIDEKICK_SETTINGS)) {\n $sidekickService = $this->getSidekickService();\n\n $sidekickData = $sidekickService->getSidekickSettingsForUser($user);\n\n $conferenceSidekickOpen = $sidekickData['conferenceSettings'];\n $softphoneSidekickOpen = $sidekickData['softphoneSettings'];\n $conferenceSidekickPopupOverridden = $sidekickData['conferenceSidekickPopupOverridden'];\n $softphoneSidekickPopupOverridden = $sidekickData['softphoneSidekickPopupOverridden'];\n $hasSidekickEnabled = $sidekickData['sidekickEnabled'];\n }\n\n $userService = $this->getUserService();\n $dealInsightsPeriod = $userService->getDealInsightTimelinePeriod($user);\n $dataFormatCountryCode = $userService->getDateTimeCountryCode($user);\n\n // Attributes for the user only.\n $attributes += [\n 'conferenceJoinReminder' => $user->conference_join_reminder,\n 'softphoneInboundRecordPreference' => $user->checkSoftphoneInboundRecordPreference(),\n 'softphoneOutboundRecordPreference' => $user->checkSoftphoneOutboundRecordPreference(),\n 'softphoneInboundDestination' => $user->softphone_inbound_destination,\n 'softphoneHasVoiceCapability' => $softphoneHasVoiceCapability,\n 'softphoneNumber' => $user->getSoftPhoneNumber(),\n 'formattedSoftphoneNumber' => $user->getFormattedSoftphoneNumberAttribute(),\n 'email' => $user->getEmailAddress(),\n 'secondaryEmail' => $user->getSecondaryEmailAddress(),\n 'phone' => $user->phone,\n 'secondaryPhone' => $user->secondary_phone,\n 'callerId' => $user->getCallerId(),\n 'countryCode' => $user->getCountryCode(),\n 'timezone' => $user->getTimezone()->getName(),\n 'language' => $user->getLanguage(),\n 'locales' => $this->container->get(UserRepository::class)->getUserLocales($user),\n 'status' => $user->getStatus(),\n 'hash' => $user->generateHash(),\n 'registrationDate' => $user->created_at ? $user->created_at->toIso8601String() : null,\n 'notifyLiveCoaching' => $user->notify_live_coaching,\n 'activityLogReminder' => $user->activity_log_reminder,\n 'conferenceSidekickOpen' => $conferenceSidekickOpen,\n 'softphoneSidekickOpen' => $softphoneSidekickOpen,\n 'conferenceSidekickPopupOverridden' => $conferenceSidekickPopupOverridden,\n 'softphoneSidekickPopupOverridden' => $softphoneSidekickPopupOverridden,\n 'hasSidekickEnabled' => $hasSidekickEnabled,\n 'activityActionItems' => $user->activity_action_items,\n 'syncEmail' => $user->isSyncEmailEnabled(),\n 'syncConference' => $user->sync_conference,\n 'syncDialer' => $user->shouldSyncDialer(),\n 'needsToConfigurePhoneNumber' => $this->container\n ->get('onboarding_phone_decider')\n ->isOnboardable($user),\n 'shouldShowPhoneNumberField' => $this->container\n ->get('onboarding_phone_decider')\n ->shouldShowPhoneNumberField($user),\n UserSetting::DEAL_INSIGHTS_TIMELINE_PERIOD => $dealInsightsPeriod,\n 'countryByTimezone' => $dataFormatCountryCode,\n 'conferenceSlug' => $user->getConferenceSlug(),\n 'conferenceRecordExternalOrganizerPreference' =>\n $userService->isConferenceRecordExternalOrganizerPreferenceEnabled($user),\n 'hasGeneratedAiReports' => $this->getAutomatedReportsRepository()->countUserReports($user) > 0,\n 'sendEmailWhenExportLinkIsOpened' => $userService->canSendEmailWhenExportLinkIsOpened($user),\n ];\n\n if ($user->softphone_debug) {\n $attributes += [\n 'debugSoftphone' => $user->softphone_debug, // Needed?\n ];\n }\n }\n\n if ($user->getTeam()->getNotificationProvider() === Team::NOTIFICATION_PROVIDER_MSTEAMS) {\n $socialAccountMS = $user->getSocialAccount(Team::CALENDAR_PROVIDER_OFFICE);\n $state = $socialAccountMS !== null\n ? str_contains($socialAccountMS->auth_scope, MsTeams::SCOPE_CHAT_CREATE)\n : null;\n\n $attributes['integrations']['office'] = [\n 'displayName' => 'Microsoft Teams',\n 'apiName' => 'microsoft-teams',\n 'types' => ['notification'],\n 'state' => $state ? Provider::STATE_INSTALLED : Provider::STATE_NOT_INSTALLED,\n 'logo' => cdn('img/ms-teams-logo.svg'),\n 'installationStrategy' => 'oauth',\n ];\n }\n\n return $attributes;\n }\n\n public function includeTeam(User $user): Item\n {\n $team = $user->getTeam();\n\n return $this->item($team, $this->getTeamTransformer());\n }\n\n public function includeGroup(User $user): ?Item\n {\n $group = $user->getGroup();\n if ($group === null) {\n return null;\n }\n\n return $this->item($group, $this->getGroupTransformer());\n }\n\n public function includeJob(User $user): ?Item\n {\n $job = $user->getJobTitle();\n\n if (! $job instanceof JobTitle) {\n return null;\n }\n\n return $this->item($job, $this->getJobTitleTransformer());\n }\n\n public function includeRoles(User $user): Resource\\Collection\n {\n /** @var Collection<int, string> $roles */\n $roles = $user->roles()\n ->where('is_visible', true)\n ->pluck('name')\n ->toArray();\n\n return $this->collection($roles, $this->getRoleTransformer());\n }\n\n public function includePermissions(User $user): Resource\\Collection\n {\n $permissions = $user->allPermissions();\n\n return $this->collection($permissions, $this->getPermissionTransformer());\n }\n\n public function includeIntegrations(User $user): Item\n {\n return $this->item($user, $this->getIntegrationsTransformer());\n }\n\n private function getTeamTransformer(): TransformerAbstract\n {\n return $this->container->get(TeamTransformer::class);\n }\n\n private function getGroupTransformer(): GroupTransformer\n {\n return $this->container->get(GroupTransformer::class);\n }\n\n private function getIntegrationsTransformer(): IntegrationTransformer\n {\n return $this->container->get(IntegrationTransformer::class);\n }\n\n private function getPermissionTransformer(): PermissionTransformer\n {\n return $this->container->get(PermissionTransformer::class);\n }\n\n private function getRoleTransformer(): RoleTransformer\n {\n return $this->container->get(RoleTransformer::class);\n }\n\n private function getJobTitleTransformer(): JobTitleTransformer\n {\n return $this->container->get(JobTitleTransformer::class);\n }\n\n private function getSidekickService(): SidekickService\n {\n /** @var SidekickService */\n return $this->container->get(SidekickService::class);\n }\n\n private function getUserService(): UserService\n {\n /** @var UserService */\n return $this->container->get(UserService::class);\n }\n\n private function getAutomatedReportsRepository(): AutomatedReportsRepository\n {\n /** @var AutomatedReportsRepository */\n return $this->container->get(AutomatedReportsRepository::class);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"36","depth":4,"bounds":{"left":0.96210104,"top":0.10055866,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9740692,"top":0.09896249,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.98138297,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\DealInsights;\n\nuse Doctrine\\DBAL\\Connection;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealData;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealsFilter;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\QueryBuilder;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\Visitor\\QueryBuilderVisitorInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\Utils\\UrlGeneratorInterface;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Illuminate\\Database\\Query\\Builder;\nuse Illuminate\\Database\\Eloquent;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nclass DealsRepository implements DealsRepositoryInterface\n{\n private Connection $connection;\n\n private ProviderRegistry $providerRegistry;\n\n /**\n * @var QueryBuilderVisitorInterface[]\n */\n private array $visitors = [];\n\n /**\n * @param QueryBuilderVisitorInterface[] $visitors\n */\n public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])\n {\n $this->connection = $connection;\n $this->providerRegistry = $crmProviderRegistry;\n\n foreach ($visitors as $visitor) {\n $this->visitors[$visitor->getIdentifier()] = $visitor;\n }\n }\n\n public function getDeals(CriteriaInterface $criteria): array\n {\n $context = $criteria->getContext();\n $team = $context->getTeam();\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n\n $this->visit($qb, $criteria);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getDeal(Team $team, int $id): array\n {\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n $qb->andWhere('opp.id = :id')->setParameter('id', $id);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')\n ->from('crm_fields', 'f')\n ->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')\n ->where('f.crm_configuration_id = :crm')\n ->andWhere('f.object_type = :type')\n ->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')\n ->orderBy('fd.object_id', 'ASC')\n ->addOrderBy('fd.updated_at', 'ASC')\n\n ->setParameter('type', Field::OBJECT_OPPORTUNITY)\n ->setParameter('crm', $crmId)\n ;\n\n if (! empty($crmFields)) {\n $fields = array_map(fn ($value): string => '\"' . $value . '\"', $crmFields);\n $qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');\n }\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAssociative();\n }\n\n public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('COALESCE(opp.currency_code, \"' . $defaultCurrency . '\") AS currency')\n ->addSelect('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ->groupBy('currency')\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getDealActivities(CriteriaInterface $criteria): array\n {\n $qb = Activity::with(['participants', 'user'])\n ->where('opportunity_id', $criteria->getOpportunityId())\n ->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())\n ->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())\n ->orderBy($criteria->getSortBy(), $criteria->getSortDirection())\n ;\n\n // Should we filter activities by criteria? It's intended to filter deals.\n\n return $qb->get()->all();\n }\n\n public function getStages(CriteriaInterface $criteria): array\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('id', 'label', 'sequence')\n ->from('stages', 's')\n ->where('crm_configuration_id = :crm_configuration_id')\n ->andWhere('type = :type')\n ->orderBy('sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())\n ->setParameter('type', Stage::TYPE_OPPORTUNITY);\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $result[$row['id']] = [\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n public function getConfigurationStages(Configuration $configuration): Collection\n {\n return $configuration\n ->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->get();\n }\n\n public function getPipelineData(Configuration $crm): array\n {\n $qb = new QueryBuilder($this->connection);\n $provider = $crm->provider;\n\n $qb\n ->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')\n ->from('stages', 's')\n ->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')\n ->where('s.crm_configuration_id = :crm_configuration_id')\n ->andWhere('s.type = :type')\n ->orderBy('bps.business_process_id', 'ASC')\n ->addOrderBy('s.sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $crm->id)\n ->setParameter('type', Stage::TYPE_OPPORTUNITY)\n ;\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];\n $result[$row['pipeline_id']][] = [\n 'value' => $value,\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n private function createQueryBuilder(string $realm): QueryBuilder\n {\n return (new QueryBuilder($this->connection))\n ->setRealm($realm)\n ->from('opportunities', 'opp')\n ->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')\n ->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')\n ->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')\n ;\n }\n\n /**\n * Applies all applicable visitors and returns the IDs of the executed ones\n *\n * @return string[]\n */\n private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array\n {\n $queryVisitors = [];\n\n foreach ($this->visitors as $visitor) {\n if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {\n $visitor->visit($queryBuilder, $criteria);\n\n $queryVisitors[] = $visitor->getIdentifier();\n }\n }\n\n return $queryVisitors;\n }\n\n private function hydrateStages(array $deals): array\n {\n foreach ($this->fetchStages(array_keys($deals)) as $stage) {\n $oppId = (int) $stage['opportunity_id'];\n\n if (! isset($deals[$oppId])) {\n continue; // or throw??!\n }\n\n $deals[$oppId]['stages'][] = [\n 'id' => $stage['stage_id'],\n 'name' => $stage['label'],\n 'enteredAt' => $stage['created_at'],\n ];\n }\n\n return $deals;\n }\n\n /**\n * @param int[] $dealIds\n */\n private function fetchStages(array $dealIds): array\n {\n if (empty($dealIds)) {\n return [];\n }\n\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')\n ->from('opportunity_stages', 'os')\n ->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')\n ->where($qb->expr()->in('os.opportunity_id', $dealIds))\n ->orderBy('os.opportunity_id', 'ASC')\n ->addOrderBy('s.created_at', 'ASC')\n ;\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array\n {\n $result = [];\n\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $data = [\n 'uuid' => RequiresUUID::toNormal($row['uuid']),\n 'name' => $row['name'],\n 'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),\n 'account' => [\n 'name' => $row['acc_name'],\n 'url' => $crmService->generateProviderUrl(\n providerId: $row['acc_provider_id'],\n objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'\n ),\n ],\n 'owner' => null,\n 'rawValue' => [\n 'amount' => (float) $row['value'],\n 'currency' => $row['currency_code'],\n ],\n 'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),\n 'openDate' => $row['remotely_created_at'] ?? null,\n 'closeDate' => $row['close_date'] ?? null,\n 'stages' => [],\n 'currentPipelineId' => $row['pipeline_id'],\n 'currentStage' => [\n 'id' => $row['stage_id'],\n 'enteredAt' => $row['stage_updated_at'],\n ],\n 'currentStageUpdatedAt' => $row['stage_updated_at'],\n 'isClosed' => (bool) $row['is_closed'],\n 'isWon' => (bool) $row['is_won'],\n ];\n\n if (isset($row['owner_uuid'])) {\n $data['owner'] = [\n 'uuid' => RequiresUUID::toNormal($row['owner_uuid']),\n 'name' => $row['owner_name'],\n 'photoUrl' => $row['owner_photo'] === null\n ? null\n : client_cdn($row['owner_photo'], $team),\n 'id' => $row['owner_id'],\n 'job' => $row['owner_job'],\n ];\n }\n\n $result[(int) $row['opp_id']] = $data;\n }\n\n return $this->hydrateStages($result);\n }\n\n private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder\n {\n $qb = clone $queryBuilder;\n $qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');\n\n $qb\n ->select(...[\n 'opp.id as opp_id',\n 'opp.uuid',\n 'opp.name',\n 'opp.value',\n 'opp.currency_code',\n 'opp.close_date',\n 'opp.remotely_created_at',\n 'opp.is_closed',\n 'opp.is_won',\n ])\n ->addSelect(...[\n 'usr.uuid as owner_uuid',\n 'usr.name AS owner_name',\n 'usr.photo_path as owner_photo',\n 'usr.id AS owner_id',\n 'jt.name as owner_job',\n ])\n ->addSelect('opp.stage_id', 'opp.stage_updated_at')\n ->addSelect(...[\n 'acc.name AS acc_name',\n 'acc.is_internal as acc_is_internal',\n 'opp.stage_updated_at',\n 'acc.crm_provider_id AS acc_provider_id',\n 'opp.crm_provider_id AS opp_provider_id',\n ])\n ->addSelect('rt.business_process_id AS pipeline_id')\n\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'));\n\n return $qb;\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws SocialAccountTokenInvalidException\n */\n private function getCrmService(Team $team): ServiceInterface\n {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n $crmService->setConfiguration($team->crm);\n if ($crmService instanceof UrlGeneratorInterface) {\n $crmService->setCrmUrlGenerator($team->crm);\n }\n\n return $crmService;\n }\n\n /**\n *\n * @return Generator<DealData>\n */\n public function getForecastData(DealsFilter $filter): Generator\n {\n $opportunities = DB::query()\n ->select([\n 'o.value',\n 'o.close_date',\n 'o.currency_code',\n 'o.is_won',\n 'o.is_closed',\n 'o.probability',\n 'o.forecast_category',\n ])\n ->from('opportunities', 'o')\n ->join('users', 'users.id', '=', 'o.user_id')\n ->join('groups', 'groups.id', '=', 'users.group_id')\n ->where('users.team_id', $filter->getTeam()->getId())\n ->where('o.close_date', '>=', $filter->getStartDate())\n ->where('o.close_date', '<=', $filter->getEndDate())\n ->where('o.currency_code', $filter->getCurrency())\n ->where('o.deleted_at', '=', null)\n ;\n\n $userUuidList = $filter->getUserUuidList();\n if (! empty($userUuidList)) {\n $userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);\n\n $opportunities->whereIn('users.uuid', $userUuidList);\n }\n\n $groupUuidList = $filter->getGroupUuidList();\n if (! empty($groupUuidList)) {\n $groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);\n\n $opportunities->whereIn('groups.uuid', $groupUuidList);\n }\n\n foreach ($opportunities->cursor() as $row) {\n yield new DealData(\n (float) $row->value,\n $row->close_date,\n ! empty($row->is_won),\n ! empty($row->is_closed),\n $row->probability ?: 0,\n $row->forecast_category ?: '',\n );\n }\n }\n\n public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection\n {\n return $user->subscriptionSets()\n ->where(static function (Eloquent\\Builder $query): void {\n $query\n ->whereNull('expired_at')\n ->orWhere('expired_at', '>=', now());\n })\n ->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {\n $join\n ->on('subscription_set_id', '=', 'activity_subscription_sets.id');\n $join\n ->where('followable_type', Models\\Activity\\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)\n ->whereIn('followable_id', $opportunityIds);\n })\n ->pluck('followable_id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Component\\DealInsights;\n\nuse Doctrine\\DBAL\\Connection;\nuse Generator;\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealData;\nuse Jiminny\\Component\\DealInsights\\Forecast\\DealsFilter;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\QueryBuilder;\nuse Jiminny\\Component\\DealInsights\\QueryBuilder\\Visitor\\QueryBuilderVisitorInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Models;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\Utils\\UrlGeneratorInterface;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Jiminny\\Traits\\RequiresUUID;\nuse Illuminate\\Database\\Query\\Builder;\nuse Illuminate\\Database\\Eloquent;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nclass DealsRepository implements DealsRepositoryInterface\n{\n private Connection $connection;\n\n private ProviderRegistry $providerRegistry;\n\n /**\n * @var QueryBuilderVisitorInterface[]\n */\n private array $visitors = [];\n\n /**\n * @param QueryBuilderVisitorInterface[] $visitors\n */\n public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])\n {\n $this->connection = $connection;\n $this->providerRegistry = $crmProviderRegistry;\n\n foreach ($visitors as $visitor) {\n $this->visitors[$visitor->getIdentifier()] = $visitor;\n }\n }\n\n public function getDeals(CriteriaInterface $criteria): array\n {\n $context = $criteria->getContext();\n $team = $context->getTeam();\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n\n $this->visit($qb, $criteria);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getDeal(Team $team, int $id): array\n {\n $crmService = $this->getCrmService($team);\n\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);\n $qb = $this->getSearchSelectAndWhereClauses($qb);\n $qb->andWhere('opp.id = :id')->setParameter('id', $id);\n\n return $this->execute($team, $crmService, $qb);\n }\n\n public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')\n ->from('crm_fields', 'f')\n ->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')\n ->where('f.crm_configuration_id = :crm')\n ->andWhere('f.object_type = :type')\n ->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')\n ->orderBy('fd.object_id', 'ASC')\n ->addOrderBy('fd.updated_at', 'ASC')\n\n ->setParameter('type', Field::OBJECT_OPPORTUNITY)\n ->setParameter('crm', $crmId)\n ;\n\n if (! empty($crmFields)) {\n $fields = array_map(fn ($value): string => '\"' . $value . '\"', $crmFields);\n $qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');\n }\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAssociative();\n }\n\n public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array\n {\n $qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);\n\n $qb\n ->select('COALESCE(opp.currency_code, \"' . $defaultCurrency . '\") AS currency')\n ->addSelect('SUM(opp.value) as total')\n ->addSelect('count(*) as `count`')\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'))\n ->groupBy('currency')\n ;\n\n $this->visit($qb, $criteria);\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n public function getDealActivities(CriteriaInterface $criteria): array\n {\n $qb = Activity::with(['participants', 'user'])\n ->where('opportunity_id', $criteria->getOpportunityId())\n ->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())\n ->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())\n ->orderBy($criteria->getSortBy(), $criteria->getSortDirection())\n ;\n\n // Should we filter activities by criteria? It's intended to filter deals.\n\n return $qb->get()->all();\n }\n\n public function getStages(CriteriaInterface $criteria): array\n {\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('id', 'label', 'sequence')\n ->from('stages', 's')\n ->where('crm_configuration_id = :crm_configuration_id')\n ->andWhere('type = :type')\n ->orderBy('sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())\n ->setParameter('type', Stage::TYPE_OPPORTUNITY);\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $result[$row['id']] = [\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n public function getConfigurationStages(Configuration $configuration): Collection\n {\n return $configuration\n ->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->get();\n }\n\n public function getPipelineData(Configuration $crm): array\n {\n $qb = new QueryBuilder($this->connection);\n $provider = $crm->provider;\n\n $qb\n ->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')\n ->from('stages', 's')\n ->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')\n ->where('s.crm_configuration_id = :crm_configuration_id')\n ->andWhere('s.type = :type')\n ->orderBy('bps.business_process_id', 'ASC')\n ->addOrderBy('s.sequence', 'ASC')\n\n ->setParameter('crm_configuration_id', $crm->id)\n ->setParameter('type', Stage::TYPE_OPPORTUNITY)\n ;\n\n $result = [];\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];\n $result[$row['pipeline_id']][] = [\n 'value' => $value,\n 'label' => $row['label'],\n 'sequence' => $row['sequence'],\n ];\n }\n\n return $result;\n }\n\n private function createQueryBuilder(string $realm): QueryBuilder\n {\n return (new QueryBuilder($this->connection))\n ->setRealm($realm)\n ->from('opportunities', 'opp')\n ->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')\n ->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')\n ->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')\n ;\n }\n\n /**\n * Applies all applicable visitors and returns the IDs of the executed ones\n *\n * @return string[]\n */\n private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array\n {\n $queryVisitors = [];\n\n foreach ($this->visitors as $visitor) {\n if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {\n $visitor->visit($queryBuilder, $criteria);\n\n $queryVisitors[] = $visitor->getIdentifier();\n }\n }\n\n return $queryVisitors;\n }\n\n private function hydrateStages(array $deals): array\n {\n foreach ($this->fetchStages(array_keys($deals)) as $stage) {\n $oppId = (int) $stage['opportunity_id'];\n\n if (! isset($deals[$oppId])) {\n continue; // or throw??!\n }\n\n $deals[$oppId]['stages'][] = [\n 'id' => $stage['stage_id'],\n 'name' => $stage['label'],\n 'enteredAt' => $stage['created_at'],\n ];\n }\n\n return $deals;\n }\n\n /**\n * @param int[] $dealIds\n */\n private function fetchStages(array $dealIds): array\n {\n if (empty($dealIds)) {\n return [];\n }\n\n $qb = new QueryBuilder($this->connection);\n\n $qb\n ->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')\n ->from('opportunity_stages', 'os')\n ->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')\n ->where($qb->expr()->in('os.opportunity_id', $dealIds))\n ->orderBy('os.opportunity_id', 'ASC')\n ->addOrderBy('s.created_at', 'ASC')\n ;\n\n return $qb->executeQuery()->fetchAllAssociative();\n }\n\n private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array\n {\n $result = [];\n\n foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {\n $data = [\n 'uuid' => RequiresUUID::toNormal($row['uuid']),\n 'name' => $row['name'],\n 'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),\n 'account' => [\n 'name' => $row['acc_name'],\n 'url' => $crmService->generateProviderUrl(\n providerId: $row['acc_provider_id'],\n objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'\n ),\n ],\n 'owner' => null,\n 'rawValue' => [\n 'amount' => (float) $row['value'],\n 'currency' => $row['currency_code'],\n ],\n 'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),\n 'openDate' => $row['remotely_created_at'] ?? null,\n 'closeDate' => $row['close_date'] ?? null,\n 'stages' => [],\n 'currentPipelineId' => $row['pipeline_id'],\n 'currentStage' => [\n 'id' => $row['stage_id'],\n 'enteredAt' => $row['stage_updated_at'],\n ],\n 'currentStageUpdatedAt' => $row['stage_updated_at'],\n 'isClosed' => (bool) $row['is_closed'],\n 'isWon' => (bool) $row['is_won'],\n ];\n\n if (isset($row['owner_uuid'])) {\n $data['owner'] = [\n 'uuid' => RequiresUUID::toNormal($row['owner_uuid']),\n 'name' => $row['owner_name'],\n 'photoUrl' => $row['owner_photo'] === null\n ? null\n : client_cdn($row['owner_photo'], $team),\n 'id' => $row['owner_id'],\n 'job' => $row['owner_job'],\n ];\n }\n\n $result[(int) $row['opp_id']] = $data;\n }\n\n return $this->hydrateStages($result);\n }\n\n private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder\n {\n $qb = clone $queryBuilder;\n $qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');\n\n $qb\n ->select(...[\n 'opp.id as opp_id',\n 'opp.uuid',\n 'opp.name',\n 'opp.value',\n 'opp.currency_code',\n 'opp.close_date',\n 'opp.remotely_created_at',\n 'opp.is_closed',\n 'opp.is_won',\n ])\n ->addSelect(...[\n 'usr.uuid as owner_uuid',\n 'usr.name AS owner_name',\n 'usr.photo_path as owner_photo',\n 'usr.id AS owner_id',\n 'jt.name as owner_job',\n ])\n ->addSelect('opp.stage_id', 'opp.stage_updated_at')\n ->addSelect(...[\n 'acc.name AS acc_name',\n 'acc.is_internal as acc_is_internal',\n 'opp.stage_updated_at',\n 'acc.crm_provider_id AS acc_provider_id',\n 'opp.crm_provider_id AS opp_provider_id',\n ])\n ->addSelect('rt.business_process_id AS pipeline_id')\n\n ->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users\n ->andWhere($qb->expr()->isNull('opp.deleted_at'));\n\n return $qb;\n }\n\n /**\n * @throws ContainerExceptionInterface\n * @throws NotFoundExceptionInterface\n * @throws SocialAccountTokenInvalidException\n */\n private function getCrmService(Team $team): ServiceInterface\n {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n $crmService->setConfiguration($team->crm);\n if ($crmService instanceof UrlGeneratorInterface) {\n $crmService->setCrmUrlGenerator($team->crm);\n }\n\n return $crmService;\n }\n\n /**\n *\n * @return Generator<DealData>\n */\n public function getForecastData(DealsFilter $filter): Generator\n {\n $opportunities = DB::query()\n ->select([\n 'o.value',\n 'o.close_date',\n 'o.currency_code',\n 'o.is_won',\n 'o.is_closed',\n 'o.probability',\n 'o.forecast_category',\n ])\n ->from('opportunities', 'o')\n ->join('users', 'users.id', '=', 'o.user_id')\n ->join('groups', 'groups.id', '=', 'users.group_id')\n ->where('users.team_id', $filter->getTeam()->getId())\n ->where('o.close_date', '>=', $filter->getStartDate())\n ->where('o.close_date', '<=', $filter->getEndDate())\n ->where('o.currency_code', $filter->getCurrency())\n ->where('o.deleted_at', '=', null)\n ;\n\n $userUuidList = $filter->getUserUuidList();\n if (! empty($userUuidList)) {\n $userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);\n\n $opportunities->whereIn('users.uuid', $userUuidList);\n }\n\n $groupUuidList = $filter->getGroupUuidList();\n if (! empty($groupUuidList)) {\n $groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);\n\n $opportunities->whereIn('groups.uuid', $groupUuidList);\n }\n\n foreach ($opportunities->cursor() as $row) {\n yield new DealData(\n (float) $row->value,\n $row->close_date,\n ! empty($row->is_won),\n ! empty($row->is_closed),\n $row->probability ?: 0,\n $row->forecast_category ?: '',\n );\n }\n }\n\n public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection\n {\n return $user->subscriptionSets()\n ->where(static function (Eloquent\\Builder $query): void {\n $query\n ->whereNull('expired_at')\n ->orWhere('expired_at', '>=', now());\n })\n ->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {\n $join\n ->on('subscription_set_id', '=', 'activity_subscription_sets.id');\n $join\n ->where('followable_type', Models\\Activity\\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)\n ->whereIn('followable_id', $opportunityIds);\n })\n ->pluck('followable_id');\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.24335106,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Dtos","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Events","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AWS","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Cache","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Country","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Database","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Datadog","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DateTime","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAggregator.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAggregatorInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseActivities.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DatasourceInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"RelatedActivity.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"RelatedActivityInterface.php, interface","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Comments","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Forecast","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Jobs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"QueryBuilder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Services","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ClosingPeriodOptionDecorator.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CreatedPeriodOptionDecorator.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Criteria.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CriteriaInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CriteriaNormalizer.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CrmService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CrmServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealContactService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsCriteriaBuilder.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsRepository.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsRepositoryInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealsServiceRepositories.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PerformanceMonitor.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodOptionDecoratorInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodService.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PeriodServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"DealRiskTypes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisk.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksRepository.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksService.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisksServiceInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRiskType.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GroupDealRiskType.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Encoding, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Encryption, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Faker, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Gecko, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Gong, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Locks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Math, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Queue, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Router, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Saml2, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"SCIM, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Seeder, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Sentry, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Serializer, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Uploader, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Utility, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Uuid, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Waveform, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Webhooks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Workflow, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dev","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dialers","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playlists","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Postmark","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"role_description":"text"}]...
|
-2965016558402427052
|
7902282280547189052
|
click
|
accessibility
|
NULL
|
"Reposit" not found, press ⌘G to search fr "Reposit" not found, press ⌘G to search from the top
text/html
text/html
text/html
Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
Reposit
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
10/10
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
13
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Http\Transformers;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Collection;
use Jiminny\Component\Sidekick\SidekickService;
use Jiminny\Exceptions\ActivityProviderException;
use Jiminny\Http\Controllers\Settings\Users\Utils\UserSetting;
use Jiminny\Models\Activity\Provider;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\JobTitle;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\UserRepository;
use Jiminny\Services\Notification\Messengers\MsTeams;
use Jiminny\Services\UserService;
use League\Fractal\Resource;
use League\Fractal\Resource\Item;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
protected array $availableIncludes = [
'team',
'group',
'job',
'roles',
'permissions',
];
private Container $container;
private bool $withSelfVisibility = false;
public function __construct(?Container $container = null)
{
$this->container = $container ?? app();
}
public function withSelfVisibility(): self
{
$this->withSelfVisibility = true;
return $this;
}
/**
* @throws ActivityProviderException
*
* @return array<string, mixed>
*/
public function transform(User $user): array
{
$attributes = [
'id' => $user->getUuid(),
'name' => $user->getName(),
'firstName' => $user->getFirstName(),
'photoUrl' => $user->getPhotoUrl(),
'conferenceRecordPreference' => $user->checkConferenceRecordPreference(),
'conferenceRecordInternalPreference' => $user->checkConferenceRecordInternalPreference(),
// DO NOT USE User::isCrmRequired as it is not hydrated when fetched from ES!
'crmRequired' => $user->crm_required,
'slackFollowUp' => $user->slack_follow_up,
];
// DO NOT USE User::getId as it is not hydrated when fetched from ES!
if ($this->withSelfVisibility || (auth()->check() && auth()->user()->id === $user->id)) {
$softphoneHasVoiceCapability = $user->hasSoftphoneNumberCapabilities()
&& $user->getSoftphoneNumberCapabilities()->hasVoiceCapability()
;
$conferenceSidekickOpen = $user->getConferenceSidekickOpen();
$softphoneSidekickOpen = $user->getSoftphoneSidekickOpen();
$conferenceSidekickPopupOverridden = false;
$softphoneSidekickPopupOverridden = false;
$hasSidekickEnabled = true;
if ($user->getTeam()->hasFeature(FeatureEnum::SIDEKICK_SETTINGS)) {
$sidekickService = $this->getSidekickService();
$sidekickData = $sidekickService->getSidekickSettingsForUser($user);
$conferenceSidekickOpen = $sidekickData['conferenceSettings'];
$softphoneSidekickOpen = $sidekickData['softphoneSettings'];
$conferenceSidekickPopupOverridden = $sidekickData['conferenceSidekickPopupOverridden'];
$softphoneSidekickPopupOverridden = $sidekickData['softphoneSidekickPopupOverridden'];
$hasSidekickEnabled = $sidekickData['sidekickEnabled'];
}
$userService = $this->getUserService();
$dealInsightsPeriod = $userService->getDealInsightTimelinePeriod($user);
$dataFormatCountryCode = $userService->getDateTimeCountryCode($user);
// Attributes for the user only.
$attributes += [
'conferenceJoinReminder' => $user->conference_join_reminder,
'softphoneInboundRecordPreference' => $user->checkSoftphoneInboundRecordPreference(),
'softphoneOutboundRecordPreference' => $user->checkSoftphoneOutboundRecordPreference(),
'softphoneInboundDestination' => $user->softphone_inbound_destination,
'softphoneHasVoiceCapability' => $softphoneHasVoiceCapability,
'softphoneNumber' => $user->getSoftPhoneNumber(),
'formattedSoftphoneNumber' => $user->getFormattedSoftphoneNumberAttribute(),
'email' => $user->getEmailAddress(),
'secondaryEmail' => $user->getSecondaryEmailAddress(),
'phone' => $user->phone,
'secondaryPhone' => $user->secondary_phone,
'callerId' => $user->getCallerId(),
'countryCode' => $user->getCountryCode(),
'timezone' => $user->getTimezone()->getName(),
'language' => $user->getLanguage(),
'locales' => $this->container->get(UserRepository::class)->getUserLocales($user),
'status' => $user->getStatus(),
'hash' => $user->generateHash(),
'registrationDate' => $user->created_at ? $user->created_at->toIso8601String() : null,
'notifyLiveCoaching' => $user->notify_live_coaching,
'activityLogReminder' => $user->activity_log_reminder,
'conferenceSidekickOpen' => $conferenceSidekickOpen,
'softphoneSidekickOpen' => $softphoneSidekickOpen,
'conferenceSidekickPopupOverridden' => $conferenceSidekickPopupOverridden,
'softphoneSidekickPopupOverridden' => $softphoneSidekickPopupOverridden,
'hasSidekickEnabled' => $hasSidekickEnabled,
'activityActionItems' => $user->activity_action_items,
'syncEmail' => $user->isSyncEmailEnabled(),
'syncConference' => $user->sync_conference,
'syncDialer' => $user->shouldSyncDialer(),
'needsToConfigurePhoneNumber' => $this->container
->get('onboarding_phone_decider')
->isOnboardable($user),
'shouldShowPhoneNumberField' => $this->container
->get('onboarding_phone_decider')
->shouldShowPhoneNumberField($user),
UserSetting::DEAL_INSIGHTS_TIMELINE_PERIOD => $dealInsightsPeriod,
'countryByTimezone' => $dataFormatCountryCode,
'conferenceSlug' => $user->getConferenceSlug(),
'conferenceRecordExternalOrganizerPreference' =>
$userService->isConferenceRecordExternalOrganizerPreferenceEnabled($user),
'hasGeneratedAiReports' => $this->getAutomatedReportsRepository()->countUserReports($user) > 0,
'sendEmailWhenExportLinkIsOpened' => $userService->canSendEmailWhenExportLinkIsOpened($user),
];
if ($user->softphone_debug) {
$attributes += [
'debugSoftphone' => $user->softphone_debug, // Needed?
];
}
}
if ($user->getTeam()->getNotificationProvider() === Team::NOTIFICATION_PROVIDER_MSTEAMS) {
$socialAccountMS = $user->getSocialAccount(Team::CALENDAR_PROVIDER_OFFICE);
$state = $socialAccountMS !== null
? str_contains($socialAccountMS->auth_scope, MsTeams::SCOPE_CHAT_CREATE)
: null;
$attributes['integrations']['office'] = [
'displayName' => 'Microsoft Teams',
'apiName' => 'microsoft-teams',
'types' => ['notification'],
'state' => $state ? Provider::STATE_INSTALLED : Provider::STATE_NOT_INSTALLED,
'logo' => cdn('img/ms-teams-logo.svg'),
'installationStrategy' => 'oauth',
];
}
return $attributes;
}
public function includeTeam(User $user): Item
{
$team = $user->getTeam();
return $this->item($team, $this->getTeamTransformer());
}
public function includeGroup(User $user): ?Item
{
$group = $user->getGroup();
if ($group === null) {
return null;
}
return $this->item($group, $this->getGroupTransformer());
}
public function includeJob(User $user): ?Item
{
$job = $user->getJobTitle();
if (! $job instanceof JobTitle) {
return null;
}
return $this->item($job, $this->getJobTitleTransformer());
}
public function includeRoles(User $user): Resource\Collection
{
/** @var Collection<int, string> $roles */
$roles = $user->roles()
->where('is_visible', true)
->pluck('name')
->toArray();
return $this->collection($roles, $this->getRoleTransformer());
}
public function includePermissions(User $user): Resource\Collection
{
$permissions = $user->allPermissions();
return $this->collection($permissions, $this->getPermissionTransformer());
}
public function includeIntegrations(User $user): Item
{
return $this->item($user, $this->getIntegrationsTransformer());
}
private function getTeamTransformer(): TransformerAbstract
{
return $this->container->get(TeamTransformer::class);
}
private function getGroupTransformer(): GroupTransformer
{
return $this->container->get(GroupTransformer::class);
}
private function getIntegrationsTransformer(): IntegrationTransformer
{
return $this->container->get(IntegrationTransformer::class);
}
private function getPermissionTransformer(): PermissionTransformer
{
return $this->container->get(PermissionTransformer::class);
}
private function getRoleTransformer(): RoleTransformer
{
return $this->container->get(RoleTransformer::class);
}
private function getJobTitleTransformer(): JobTitleTransformer
{
return $this->container->get(JobTitleTransformer::class);
}
private function getSidekickService(): SidekickService
{
/** @var SidekickService */
return $this->container->get(SidekickService::class);
}
private function getUserService(): UserService
{
/** @var UserService */
return $this->container->get(UserService::class);
}
private function getAutomatedReportsRepository(): AutomatedReportsRepository
{
/** @var AutomatedReportsRepository */
return $this->container->get(AutomatedReportsRepository::class);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
36
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Component\DealInsights;
use Doctrine\DBAL\Connection;
use Generator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Jiminny\Component\DealInsights\Forecast\DealData;
use Jiminny\Component\DealInsights\Forecast\DealsFilter;
use Jiminny\Component\DealInsights\QueryBuilder\QueryBuilder;
use Jiminny\Component\DealInsights\QueryBuilder\Visitor\QueryBuilderVisitorInterface;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Stage;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Models;
use Jiminny\Services\Crm\IntegrationApp\DTO\Utils\UrlGeneratorInterface;
use Jiminny\Services\Crm\ProviderRegistry;
use Jiminny\Traits\RequiresUUID;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Eloquent;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class DealsRepository implements DealsRepositoryInterface
{
private Connection $connection;
private ProviderRegistry $providerRegistry;
/**
* @var QueryBuilderVisitorInterface[]
*/
private array $visitors = [];
/**
* @param QueryBuilderVisitorInterface[] $visitors
*/
public function __construct(Connection $connection, ProviderRegistry $crmProviderRegistry, array $visitors = [])
{
$this->connection = $connection;
$this->providerRegistry = $crmProviderRegistry;
foreach ($visitors as $visitor) {
$this->visitors[$visitor->getIdentifier()] = $visitor;
}
}
public function getDeals(CriteriaInterface $criteria): array
{
$context = $criteria->getContext();
$team = $context->getTeam();
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$this->visit($qb, $criteria);
return $this->execute($team, $crmService, $qb);
}
public function getDeal(Team $team, int $id): array
{
$crmService = $this->getCrmService($team);
$qb = $this->createQueryBuilder(QueryBuilder::REALM_DEALS);
$qb = $this->getSearchSelectAndWhereClauses($qb);
$qb->andWhere('opp.id = :id')->setParameter('id', $id);
return $this->execute($team, $crmService, $qb);
}
public function getCrmFieldData(array $crmFields, int $crmId, array $opportunityIds = [])
{
$qb = new QueryBuilder($this->connection);
$qb
->select('f.id', 'f.crm_provider_id AS field_name', 'f.label', 'fd.object_id AS dealId', 'fd.value')
->from('crm_fields', 'f')
->join('f', 'crm_field_data', 'fd', 'fd.crm_field_id = f.id')
->where('f.crm_configuration_id = :crm')
->andWhere('f.object_type = :type')
->andWhere('fd.object_id IN (' . implode(',', $opportunityIds) . ')')
->orderBy('fd.object_id', 'ASC')
->addOrderBy('fd.updated_at', 'ASC')
->setParameter('type', Field::OBJECT_OPPORTUNITY)
->setParameter('crm', $crmId)
;
if (! empty($crmFields)) {
$fields = array_map(fn ($value): string => '"' . $value . '"', $crmFields);
$qb->andWhere('f.crm_provider_id IN (' . implode(',', $fields) . ')');
}
return $qb->executeQuery()->fetchAllAssociative();
}
public function getTotalsInDefaultCurrency(CriteriaInterface $criteria): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAssociative();
}
public function getTotals(CriteriaInterface $criteria, string $defaultCurrency): array
{
$qb = $this->createQueryBuilder(QueryBuilder::REALM_TOTALS);
$qb
->select('COALESCE(opp.currency_code, "' . $defaultCurrency . '") AS currency')
->addSelect('SUM(opp.value) as total')
->addSelect('count(*) as `count`')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not include deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'))
->groupBy('currency')
;
$this->visit($qb, $criteria);
return $qb->executeQuery()->fetchAllAssociative();
}
public function getDealActivities(CriteriaInterface $criteria): array
{
$qb = Activity::with(['participants', 'user'])
->where('opportunity_id', $criteria->getOpportunityId())
->whereDate('actual_start_time', '>=', $criteria->getPeriod()->getStartDate())
->whereDate('actual_start_time', '<=', $criteria->getPeriod()->getEndDate())
->orderBy($criteria->getSortBy(), $criteria->getSortDirection())
;
// Should we filter activities by criteria? It's intended to filter deals.
return $qb->get()->all();
}
public function getStages(CriteriaInterface $criteria): array
{
$qb = new QueryBuilder($this->connection);
$qb
->select('id', 'label', 'sequence')
->from('stages', 's')
->where('crm_configuration_id = :crm_configuration_id')
->andWhere('type = :type')
->orderBy('sequence', 'ASC')
->setParameter('crm_configuration_id', $criteria->getContext()->getTeam()->getCrmConfiguration()->getId())
->setParameter('type', Stage::TYPE_OPPORTUNITY);
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$result[$row['id']] = [
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
public function getConfigurationStages(Configuration $configuration): Collection
{
return $configuration
->stages()
->where('type', Stage::TYPE_OPPORTUNITY)
->get();
}
public function getPipelineData(Configuration $crm): array
{
$qb = new QueryBuilder($this->connection);
$provider = $crm->provider;
$qb
->select('s.label', 's.crm_provider_id', 's.sequence', 'bps.business_process_id AS pipeline_id')
->from('stages', 's')
->join('s', 'business_process_stages', 'bps', 's.id=bps.stage_id')
->where('s.crm_configuration_id = :crm_configuration_id')
->andWhere('s.type = :type')
->orderBy('bps.business_process_id', 'ASC')
->addOrderBy('s.sequence', 'ASC')
->setParameter('crm_configuration_id', $crm->id)
->setParameter('type', Stage::TYPE_OPPORTUNITY)
;
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$value = $provider === Configuration::PROVIDER_SALESFORCE ? $row['label'] : $row['crm_provider_id'];
$result[$row['pipeline_id']][] = [
'value' => $value,
'label' => $row['label'],
'sequence' => $row['sequence'],
];
}
return $result;
}
private function createQueryBuilder(string $realm): QueryBuilder
{
return (new QueryBuilder($this->connection))
->setRealm($realm)
->from('opportunities', 'opp')
->leftJoin('opp', 'record_types', 'rt', 'opp.record_type_id = rt.id')
->leftJoin('opp', 'users', 'usr', 'opp.user_id = usr.id')
->leftJoin('opp', 'accounts', 'acc', 'opp.account_id = acc.id')
;
}
/**
* Applies all applicable visitors and returns the IDs of the executed ones
*
* @return string[]
*/
private function visit(QueryBuilder $queryBuilder, CriteriaInterface $criteria): array
{
$queryVisitors = [];
foreach ($this->visitors as $visitor) {
if ($visitor->isSatisfiedBy($criteria, $queryBuilder->getRealm())) {
$visitor->visit($queryBuilder, $criteria);
$queryVisitors[] = $visitor->getIdentifier();
}
}
return $queryVisitors;
}
private function hydrateStages(array $deals): array
{
foreach ($this->fetchStages(array_keys($deals)) as $stage) {
$oppId = (int) $stage['opportunity_id'];
if (! isset($deals[$oppId])) {
continue; // or throw??!
}
$deals[$oppId]['stages'][] = [
'id' => $stage['stage_id'],
'name' => $stage['label'],
'enteredAt' => $stage['created_at'],
];
}
return $deals;
}
/**
* @param int[] $dealIds
*/
private function fetchStages(array $dealIds): array
{
if (empty($dealIds)) {
return [];
}
$qb = new QueryBuilder($this->connection);
$qb
->select('os.opportunity_id', 's.id AS stage_id', 's.label', 's.created_at')
->from('opportunity_stages', 'os')
->leftJoin('os', 'stages', 's', 'os.stage_id=s.id')
->where($qb->expr()->in('os.opportunity_id', $dealIds))
->orderBy('os.opportunity_id', 'ASC')
->addOrderBy('s.created_at', 'ASC')
;
return $qb->executeQuery()->fetchAllAssociative();
}
private function execute(Team $team, ServiceInterface $crmService, QueryBuilder $qb): array
{
$result = [];
foreach ($qb->executeQuery()->fetchAllAssociative() as $row) {
$data = [
'uuid' => RequiresUUID::toNormal($row['uuid']),
'name' => $row['name'],
'url' => $crmService->generateProviderUrl($row['opp_provider_id'], 'opportunity'),
'account' => [
'name' => $row['acc_name'],
'url' => $crmService->generateProviderUrl(
providerId: $row['acc_provider_id'],
objectType: $row['acc_is_internal'] ? 'internal-account' : 'account'
),
],
'owner' => null,
'rawValue' => [
'amount' => (float) $row['value'],
'currency' => $row['currency_code'],
],
'value' => formatOpportunityValue((float) $row['value'], $row['currency_code']),
'openDate' => $row['remotely_created_at'] ?? null,
'closeDate' => $row['close_date'] ?? null,
'stages' => [],
'currentPipelineId' => $row['pipeline_id'],
'currentStage' => [
'id' => $row['stage_id'],
'enteredAt' => $row['stage_updated_at'],
],
'currentStageUpdatedAt' => $row['stage_updated_at'],
'isClosed' => (bool) $row['is_closed'],
'isWon' => (bool) $row['is_won'],
];
if (isset($row['owner_uuid'])) {
$data['owner'] = [
'uuid' => RequiresUUID::toNormal($row['owner_uuid']),
'name' => $row['owner_name'],
'photoUrl' => $row['owner_photo'] === null
? null
: client_cdn($row['owner_photo'], $team),
'id' => $row['owner_id'],
'job' => $row['owner_job'],
];
}
$result[(int) $row['opp_id']] = $data;
}
return $this->hydrateStages($result);
}
private function getSearchSelectAndWhereClauses(QueryBuilder $queryBuilder): QueryBuilder
{
$qb = clone $queryBuilder;
$qb->leftJoin('usr', 'job_titles', 'jt', 'usr.job_title_id = jt.id');
$qb
->select(...[
'opp.id as opp_id',
'opp.uuid',
'opp.name',
'opp.value',
'opp.currency_code',
'opp.close_date',
'opp.remotely_created_at',
'opp.is_closed',
'opp.is_won',
])
->addSelect(...[
'usr.uuid as owner_uuid',
'usr.name AS owner_name',
'usr.photo_path as owner_photo',
'usr.id AS owner_id',
'jt.name as owner_job',
])
->addSelect('opp.stage_id', 'opp.stage_updated_at')
->addSelect(...[
'acc.name AS acc_name',
'acc.is_internal as acc_is_internal',
'opp.stage_updated_at',
'acc.crm_provider_id AS acc_provider_id',
'opp.crm_provider_id AS opp_provider_id',
])
->addSelect('rt.business_process_id AS pipeline_id')
->where($qb->expr()->isNotNull('opp.user_id')) // we should not display deals owned by external users
->andWhere($qb->expr()->isNull('opp.deleted_at'));
return $qb;
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws SocialAccountTokenInvalidException
*/
private function getCrmService(Team $team): ServiceInterface
{
$crmService = $this->providerRegistry->get($team->crm->provider);
$crmService->setConfiguration($team->crm);
if ($crmService instanceof UrlGeneratorInterface) {
$crmService->setCrmUrlGenerator($team->crm);
}
return $crmService;
}
/**
*
* @return Generator<DealData>
*/
public function getForecastData(DealsFilter $filter): Generator
{
$opportunities = DB::query()
->select([
'o.value',
'o.close_date',
'o.currency_code',
'o.is_won',
'o.is_closed',
'o.probability',
'o.forecast_category',
])
->from('opportunities', 'o')
->join('users', 'users.id', '=', 'o.user_id')
->join('groups', 'groups.id', '=', 'users.group_id')
->where('users.team_id', $filter->getTeam()->getId())
->where('o.close_date', '>=', $filter->getStartDate())
->where('o.close_date', '<=', $filter->getEndDate())
->where('o.currency_code', $filter->getCurrency())
->where('o.deleted_at', '=', null)
;
$userUuidList = $filter->getUserUuidList();
if (! empty($userUuidList)) {
$userUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $userUuidList);
$opportunities->whereIn('users.uuid', $userUuidList);
}
$groupUuidList = $filter->getGroupUuidList();
if (! empty($groupUuidList)) {
$groupUuidList = array_map(fn ($uuid) => RequiresUUID::toOptimized($uuid), $groupUuidList);
$opportunities->whereIn('groups.uuid', $groupUuidList);
}
foreach ($opportunities->cursor() as $row) {
yield new DealData(
(float) $row->value,
$row->close_date,
! empty($row->is_won),
! empty($row->is_closed),
$row->probability ?: 0,
$row->forecast_category ?: '',
);
}
}
public function getUserOpportunitySubscriptions(User $user, array $opportunityIds): Collection
{
return $user->subscriptionSets()
->where(static function (Eloquent\Builder $query): void {
$query
->whereNull('expired_at')
->orWhere('expired_at', '>=', now());
})
->join('activity_subscriptions', function (Builder $join) use ($opportunityIds) {
$join
->on('subscription_set_id', '=', 'activity_subscription_sets.id');
$join
->where('followable_type', Models\Activity\Subscription::FOLLOWABLE_TYPE_OPPORTUNITY)
->whereIn('followable_id', $opportunityIds);
})
->pluck('followable_id');
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Acl
ActionItems
Activity
ActivityAnalytics
ActivitySearch
AiActivityType
AiAutomation
AiCallScoring
AskAnything
Dtos
Events
AskAnythingPromptService.php, class
HistoryService.php, class
AskJiminnyAi
AWS
BillingManagement
Cache
CoachingFeedback
Country
CustomerApi
Database
Datadog
DateTime
DealInsights
Activity
ActivityAggregator.php, class
ActivityAggregatorInterface.php, interface
DatabaseActivities.php, class
DatasourceInterface.php, interface
RelatedActivity.php, class
RelatedActivityInterface.php, interface
Commands
Comments
Forecast
Jobs
QueryBuilder
Services
ClosingPeriodOptionDecorator.php, class
CreatedPeriodOptionDecorator.php, class
Criteria.php, class
CriteriaInterface.php, interface
CriteriaNormalizer.php, class
CrmService.php, class
CrmServiceInterface.php, interface
DealContactService.php, class
DealInsightsCriteriaBuilder.php, class
DealService.php, class
DealServiceInterface.php, interface
DealsRepository.php, class
DealsRepositoryInterface.php, interface
DealsServiceRepositories.php, class
PerformanceMonitor.php, class
PeriodOptionDecoratorInterface.php, interface
PeriodService.php, final class
PeriodServiceInterface.php, interface
DealRisks
DealRiskTypes
DealRisk.php, class
DealRisksRepository.php, class
DealRisksService.php, class
DealRisksServiceInterface.php, interface
DealRiskType.php
GroupDealRiskType.php
ElasticSearch, folder
Eloquent, folder
Encoding, folder
Encryption, folder
ES, folder
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection
LiveFeed
Locks, folder
Math, folder
MediaPipeline, folder
MeetingBot, folder
MobileSettings, folder
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage, folder
Playlist, folder
Prophet, folder
ProphetAi, folder
ProsperWorks, folder
Queue, folder
Router, folder
Saml2, folder
SCIM, folder
Seeder, folder
Sentry, folder
Serializer, folder
Settings, folder
Sidekick, folder
Slack, folder
TeamInsights, folder
TimeMemoryMapper, folder
Transcription, folder
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription...
|
NULL
|
|
63050
|
1362
|
46
|
2026-04-21T08:28:32.801510+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776760112801_m2.jpg...
|
PhpStorm
|
faVsco.js – Service.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
"LeadConverted" not found, press ⌘G to sea "LeadConverted" not found, press ⌘G to search from the top
text/html
text/html
text/html
Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LeadConverted
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
4/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
19
144
3
22
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Crm\Salesforce;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Jiminny\Component\Country\CountriesMap;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Services\Crm\FetchRelatedActivityInterface;
use Jiminny\Contracts\Services\Crm\LayoutManagementInterface;
use Jiminny\Contracts\Services\Crm\MatchCrmEntitiesInterface;
use Jiminny\Contracts\Services\Crm\Provider\SalesforceBatchSyncInterface;
use Jiminny\Contracts\Services\Crm\Provider\SalesforceInterface;
use Jiminny\Contracts\Services\Crm\RemoteEntityLookupInterface;
use Jiminny\Contracts\Services\Crm\RemoteEntityManipulationInterface;
use Jiminny\Contracts\Services\Crm\RemoteNoteEntityManipulationInterface;
use Jiminny\Contracts\Services\Crm\SearchTaskInterface;
use Jiminny\Contracts\Services\Crm\SendSummaryToCrmInterface;
use Jiminny\Contracts\Services\Crm\SettingsInterface;
use Jiminny\Contracts\Services\Crm\SupportsObjectTypeParseInterface;
use Jiminny\Contracts\Services\Crm\SyncCrmEntitiesInterface;
use Jiminny\Contracts\Services\Crm\SyncCrmProfileRecordTypesInterface;
use Jiminny\Contracts\Services\Crm\VerifyTaskExistsInterface;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Activities\Crm\LeadConverted;
use Jiminny\Events\Activities\Crm\ActivityLeadConverted;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\HttpBadRequestException;
use Jiminny\Exceptions\HttpNotFoundException;
use Jiminny\Exceptions\NoResultsException;
use Jiminny\Exceptions\ServiceUnavailableException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Account;
use Jiminny\Models\Activity;
use Jiminny\Models\Calendar\CalendarEvent;
use Jiminny\Models\Contact;
use Jiminny\Models\Contracts\ActivityContract;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Crm\ContactRole;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Crm\RecordType;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Lead;
use Jiminny\Models\Opportunity;
use Jiminny\Models\Playbook;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Stage;
use Jiminny\Models\TeamSettings;
use Jiminny\Models\User;
use Jiminny\Repositories\Crm\ContactRoleRepository;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\Crm\ProfileRepository;
use Jiminny\Repositories\Crm\RecordTypeFieldValuesRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\BaseService;
use Jiminny\Services\Crm\Helpers\ArrayIterator;
use Jiminny\Services\Crm\MatchDomainByEmailInterface;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Services\Crm\ResolveCompanyNameByEmailTrait;
use Jiminny\Services\Crm\Salesforce\Fields\FieldTypeConverter;
use Jiminny\Services\Crm\Salesforce\Fields\ValueNormalizer;
use Jiminny\Services\Crm\Salesforce\ServiceTraits\RecordManipulationsTrait;
use Jiminny\Services\Crm\Salesforce\ServiceTraits\SyncFieldsTrait;
use Jiminny\Utils\CurrencyFormatter;
use Jiminny\Utils\StringUtil;
use Ramsey\Uuid\Uuid;
use Sentry\Laravel\Facade as Sentry;
class Service extends BaseService implements
SalesforceInterface,
SalesforceBatchSyncInterface,
SyncCrmEntitiesInterface,
SyncCrmProfileRecordTypesInterface,
RemoteEntityManipulationInterface,
FetchRelatedActivityInterface,
SendSummaryToCrmInterface,
MatchDomainByEmailInterface,
SearchTaskInterface,
LayoutManagementInterface,
SettingsInterface,
MatchCrmEntitiesInterface,
RemoteEntityLookupInterface,
SupportsObjectTypeParseInterface,
RemoteNoteEntityManipulationInterface,
VerifyTaskExistsInterface
{
use ResolveCompanyNameByEmailTrait;
use SyncFieldsTrait;
use DeleteObjectsTrait;
use RecordManipulationsTrait;
use ServiceTraits\BatchSyncTrait;
/**
* Note Body Limit for the Old Note-Taking Tool
*
* @var int
*/
private const int CLASSIC_NOTE_MAX_LENGTH = 32000;
/**
* Note Content Limit for the New Notes
*
* @var int
*/
private const int ENHANCED_NOTE_MAX_LENGTH = 50000000;
private const string INSTALLED_PACKAGE_ID = '033Tw0000007bKbIAI';
private const int CACHE_TTL = 600;
private const int TASK_VERIFICATION_CACHE_TTL = 86400; // 1 day - 86400
/**
* @var Client
*/
protected $client;
private PayloadBuilder $payloadBuilder;
private QueryHandler $queryHandler;
private OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
public function __construct(
Client $client,
PayloadBuilder $payloadBuilder,
private readonly CountriesMap $countriesMap,
private readonly ProspectPhotoPathService $prospectPhotoPathService,
) {
parent::__construct();
$this->client = $client;
$this->payloadBuilder = $payloadBuilder;
$this->queryHandler = app(QueryHandler::class, [
'client' => $this->client,
'logger' => $this->logger,
]);
$this->opportunitySyncStrategyResolver = app(OpportunitySyncStrategyResolver::class, [
'client' => $this->client,
]);
}
public function getDisplayName(): string
{
return 'Salesforce';
}
public function getJobDelay(): int
{
return 1;
}
protected function getOAuthAccount(User $user): ?SocialAccount
{
return $user->getSocialAccount(SocialAccount::PROVIDER_SALESFORCE);
}
public function verifyTaskExists(Activity $activity): bool
{
$crmProviderId = $activity->getCrmProviderId();
$cacheKey = "crm_task_exists:{$this->config->getId()}:$crmProviderId";
return Cache::remember($cacheKey, self::TASK_VERIFICATION_CACHE_TTL, function () use ($activity, $crmProviderId) {
$playbook = $this->getPlaybookFromActivity($activity);
if ($playbook === null) {
$this->logger->warning('[Salesforce] Cannot verify task - no playbook found', [
'activity' => $activity->getId(),
'crm_provider_id' => $crmProviderId,
]);
return false;
}
$objectType = $playbook->getActivityType() === Playbook::ACTIVITY_TYPE_EVENT ? 'Event' : 'Task';
try {
$record = $this->getRecord($objectType, $crmProviderId, ['Id', 'IsDeleted']);
return ! empty($record) && ($record['IsDeleted'] ?? false) === false;
} catch (HttpNotFoundException|HttpBadRequestException) {
$this->logger->info('[Salesforce] Activity record not found during verification', [
'activity' => $activity->getId(),
'object_type' => $objectType,
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return false;
}
});
}
public function query(string $queryToRun, array $parameters = []): QueryIterator
{
// Due to poorly designed external calls, this method cannot be entirely removed
return $this->queryHandler->query($queryToRun, $parameters);
}
/*=========== Organization Information ===============*/
/**
* Get a list of all the API Versions for the instance.
*
* @throws CrmException
*
* @return mixed
*
*/
public function getApiVersions()
{
$url = $this->config->crm_base_url . '/services/data';
$response = $this->client->get($url);
return json_decode($response->getBody(), true);
}
/**
* Gets the valid recordTypes for a given Salesforce Object via the describe API.
*
* @param string $crmObject The name of the Salesforce object. i.e. Account or Contact
*
* @return array The API output, converted from JSON to an associative array.
*/
public function getRecordTypes(string $crmObject): array
{
$url = $this->client->getObjectsUrl() . $crmObject . '/describe';
$response = $this->client->get($url);
$jsonResponse = json_decode($response->getBody(), true);
$fields = [];
foreach ($jsonResponse['recordTypeInfos'] as $row) {
$fields[] = ['recordTypeId' => $row['recordTypeId'], 'default' => $row['defaultRecordTypeMapping']];
}
return $fields;
}
/**
* Convert raw field data into a format compatible with CRM APIs.
*/
public function normalizeValue(string $fieldType, string $fieldValue, bool $internal = false): string
{
return ValueNormalizer::normalize($fieldType, $fieldValue, $internal);
}
/**
* @inheritdoc
*/
public function getDefaultFields(string $activityType): array
{
$fields = [];
$defaultFields = ($activityType === Playbook::ACTIVITY_TYPE_TASK)
? FieldDefinitions::defaultTaskFields()
: FieldDefinitions::defaultEventFields();
// This lazy creates these fields if not already setup.
foreach ($defaultFields as $defaultField) {
$fields[] = $this->config->fields()->firstOrCreate($defaultField);
}
return $fields;
}
/**
* @inheritdoc
*/
public function getDefaultActivityField(string $activityType): Field
{
// Setup the activity field as the default Type.
/** @var Field $activityField */
$activityField = $this->config->fields()->where([
'crm_provider_id' => 'Type',
'object_type' => $activityType,
])->first();
return $activityField;
}
/**
* @inheritdoc
*/
public function getSupportedPlaybookTypes(): array
{
return [Playbook::ACTIVITY_TYPE_TASK, Playbook::ACTIVITY_TYPE_EVENT];
}
protected function getDefaultFollowupLayoutFields(string $activityType): array
{
$fields = [];
$fieldRepo = app(FieldRepository::class);
$fieldFilter = ($activityType === Playbook::ACTIVITY_TYPE_TASK)
? FieldDefinitions::taskFollowupFieldsFilter()
: FieldDefinitions::eventFollowupFieldsFilter();
foreach ($fieldFilter as $eachFilter) {
$field = $fieldRepo->findOneConfigurationFieldByProperties($this->config, $eachFilter);
// Only add the field if it is created, which it should be.
if ($field) {
$fields[] = $field;
}
}
return $fields;
}
public function getDealInsightsFields(): array
{
return FieldDefinitions::dealInsightsFields();
}
private function isCustomField(Field $field): bool
{
return substr($field->crm_provider_id, -\strlen('__c')) === '__c';
}
/**
* This one is now called only when ImportActivityTypes is triggered or SyncFieldMetadata executed manually
* Regular sync now uses SharedSyncFieldsTrait -> syncSingleObjectType
* Needs to be replaced later on
*/
public function syncField(Field $field): void
{
try {
if ($this->isCustomField($field)) {
$query = '
SELECT
Id, Metadata, TableEnumOrId
FROM
CustomField
WHERE
DeveloperName = :fieldName
AND
TableEnumOrId = :fieldType
AND
NamespacePrefix = :namespacePrefix';
// We need to constrain the field lookup to the object, in case it's used in multiple places.
$objectType = \in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true)
? 'activity'
: $field->object_type;
$sfFields = $this->queryHandler->metadata($query, [
'fieldName' => substr($field->crm_provider_id, 0, -\strlen('__c')),
'fieldType' => ucfirst($objectType),
// This is used to ensure we only consider the field within the org, not installed packages.
'namespacePrefix' => 'null',
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
// Sync field metadata.
$metadata = $sfField['Metadata'];
$field->description = mb_strimwidth($metadata['description'] ?? '', 0, 191);
$field->label = mb_strimwidth($metadata['label'] ?? '', 0, Field::LABEL_MAX_LENGTH);
$field->type = $this->convertFieldType($metadata['type'], $field->getEntityName());
$field->is_mandatory = ($metadata['required'] === true);
$field->length = $metadata['length'];
$field->default_value = mb_strimwidth(trim($metadata['defaultValue'] ?? '', '"'), 0, 191);
$field->save();
} else {
$query = '
SELECT
Id, DataType, DeveloperName, Label, Length, Description
FROM
FieldDefinition
WHERE
DurableId = :entityName';
$entityName = $field->getEntityName();
$sfFields = $this->queryHandler->metadata($query, [
'entityName' => $entityName,
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
$convertedType = $this->convertFieldType($sfField['DataType'], $entityName);
$label = mb_strimwidth($sfField['Label'], 0, Field::LABEL_MAX_LENGTH);
if ($field->isBusinessType()) {
$label = 'Opportunity Type';
}
$field->description = mb_strimwidth($sfField['Description'], 0, Field::DESCRIPTION_MAX_LENGTH);
$field->label = $label;
$field->type = $convertedType;
$field->length = $sfField['Length'];
$field->save();
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
private function convertFieldType(string $from, ?string $entityName = null): string
{
$converter = new FieldTypeConverter();
return $converter->convert($from, $entityName);
}
/**
* @inheritdoc
*/
public function importPicklistValues(Field $field): array
{
$values = [];
$fieldValues = [];
try {
if ($this->isCustomField($field)) {
$query = '
SELECT
Id, Metadata, TableEnumOrId
FROM
CustomField
WHERE
DeveloperName = :fieldName
AND
TableEnumOrId = :fieldType
AND
NamespacePrefix = :namespacePrefix';
// We need to constrain the field lookup to the object, in case it's used in multiple places.
$objectType = \in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true) ?
'activity' : $field->object_type;
$sfFields = $this->queryHandler->metadata($query, [
'fieldName' => substr($field->crm_provider_id, 0, -\strlen('__c')),
'fieldType' => ucfirst($objectType),
// This is used to ensure we only consider the field within the org, not installed packages.
'namespacePrefix' => 'null',
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
$valueSet = $sfField['Metadata']['valueSet'];
if ($valueSet['valueSetName'] === null) {
// Local picklist values can be obtained easily.
$picklistValues = $valueSet['valueSetDefinition']['value'];
} else {
// But for some fields, we just get the Global Value Picklist pointer so need to do more work.
$picklistValues = $this->importGlobalValuePicklistValues($valueSet['valueSetName']);
}
// Import all active values.
foreach ($picklistValues as $i => $sfFieldValue) {
// Setup default value.
if ($sfFieldValue['default']) {
$field->update(['default_value' => $sfFieldValue['valueName']]);
}
// This comes through as null if active (lol).
if ($sfFieldValue['isActive'] !== false) {
$values[] = [
'value' => $sfFieldValue['valueName'],
'label' => $sfFieldValue['valueName'],
'sequence' => $i,
'is_default' => $sfFieldValue['default'],
];
}
}
} else {
$objectFields = $this->getObjectFields($field->object_type);
$fieldId = $field->crm_provider_id;
// Only work with our field of interest.
$objectField = array_filter($objectFields, function ($item) use ($fieldId) {
return $item['name'] === $fieldId;
});
$objectField = array_shift($objectField);
if (empty($objectField['picklistValues']) === false) {
foreach ($objectField['picklistValues'] as $i => $sfFieldValue) {
// Skip inactive values.
if ($sfFieldValue['active'] === false) {
continue;
}
// Setup default value.
if ($sfFieldValue['defaultValue']) {
$field->update(['default_value' => $sfFieldValue['value']]);
}
$values[] = [
'value' => $sfFieldValue['value'],
'label' => $sfFieldValue['label'],
'sequence' => $i,
'is_default' => $sfFieldValue['defaultValue'],
];
}
}
}
$fieldsToPurge = $field->values()->get()->pluck('value')->toArray();
foreach ($values as $value) {
$value['value'] = substr($value['value'] ?? '', 0, 255);
$fieldValues[] = $field->values()->updateOrCreate([
'value' => $value['value'],
], $value);
// Remove this value from the ones we are going to purge.
if (($key = array_search($value['value'], $fieldsToPurge, true)) !== false) {
unset($fieldsToPurge[$key]);
}
}
// Delete the old values that are no longer used.
// Get IDs of the values to be deleted
$valuesToDelete = $field->values()->whereIn('value', $fieldsToPurge);
$valuesToDeleteIds = $valuesToDelete->pluck('id');
if (! $valuesToDeleteIds->isEmpty()) {
$recordTypeFieldValuesRepository = app(RecordTypeFieldValuesRepository::class);
$recordTypeFieldValuesRepository->deleteForCrmFieldValueIds($valuesToDeleteIds->toArray());
// Now safely delete from crm_field_values
$valuesToDelete->delete();
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
return $fieldValues;
}
/**
* Gets values from Global Value Picklists.
*/
private function importGlobalValuePicklistValues(string $picklistName): array
{
$query = '
SELECT
Metadata
FROM
GlobalValueSet
WHERE
DeveloperName = :picklistName
LIMIT 1';
try {
$sfValues = $this->queryHandler->metadata($query, [
'picklistName' => $picklistName,
]);
// There is always 1 result at this point.
$sfValue = $sfValues->current();
return $sfValue['Metadata']['customValue'];
} catch (NoResultsException $noResultsException) {
// Nothing returned.
return [];
}
}
/**
* @inheritdoc
*/
public function syncProfileRecordTypes(): void
{
$objectTypes = [
'lead',
'account',
'contact',
'opportunity',
'task',
'event',
];
foreach ($objectTypes as $objectType) {
try {
$crmRecordTypes = $this->getRecordTypes(ucfirst($objectType));
foreach ($crmRecordTypes as $crmRecordType) {
// If the record type is default and not the Master type, set this.
if ($crmRecordType['default'] && $crmRecordType['recordTypeId'] !== '012000000000000AAA') {
$recordType = $this->config->recordTypes()
->where('crm_provider_id', $crmRecordType['recordTypeId'])
->first();
if ($recordType) {
$this->profile->{$objectType . '_record_type_id'} = $recordType->id;
}
}
}
} catch (HttpNotFoundException $exception) {
Log::error('No access to ' . $objectType . ' object, skipping...');
// XXX: should we log this fact somewhere?
continue;
}
}
if ($this->profile->isDirty()) {
$this->profile->save();
}
}
/**
* Gets business processes.
*/
public function importBusinessProcesses(): void
{
$query = '
SELECT
Id, IsActive, Name, TableEnumOrId
FROM
BusinessProcess
WHERE
TableEnumOrId IN (\'Lead\',\'Opportunity\')';
try {
$sfProcesses = $this->queryHandler->query($query);
// Upsert all processes for the team.
foreach ($sfProcesses as $sfProcess) {
/** @var BusinessProcess $businessProcess */
$businessProcess = $this->config->businessProcesses()->updateOrCreate([
'crm_provider_id' => $sfProcess['Id'],
], [
'team_id' => $this->team->id,
'name' => $sfProcess['Name'],
'type' => $sfProcess['TableEnumOrId'] === 'Lead' ? 'lead' : 'opportunity',
'is_selectable' => $sfProcess['IsActive'],
]);
$this->importBusinessProcessStages($businessProcess);
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
/**
* Gets business process stages.
*/
public function importBusinessProcessStages(BusinessProcess $businessProcess): void
{
$query = '
SELECT
Metadata
FROM
BusinessProcess
WHERE
Id = :processId';
try {
$stages = [];
$sfProcessStages = $this->queryHandler->metadata($query, [
'processId' => $businessProcess->crm_provider_id,
]);
// There is always 1 result at this point.
$sfProcessStage = $sfProcessStages->current();
// Upsert all processes for the team.
foreach ($sfProcessStage['Metadata']['values'] as $sfProcessStage) {
$sanitizedName = urldecode($sfProcessStage['valueName']); // Must decode: "%2C" becomes "," etc.
$stage = $businessProcess->crm->stages()
// This MUST match on label because this API doesn't use API Name.
->where('label', $sanitizedName)
->where('type', $businessProcess->type)
->where('is_selectable', 1)
->first();
if ($stage) {
$stages[] = $stage->id;
}
}
$businessProcess->stages()->sync($stages);
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
/**
* Gets record types.
*/
public function importRecordTypes(): void
{
$query = '
SELECT
Id, IsActive, Name, BusinessProcessId, SobjectType
FROM
RecordType';
try {
$sfRecordTypes = $this->queryHandler->query($query);
// Upsert all record types for the process.
foreach ($sfRecordTypes as $sfRecordType) {
$businessProcess = null;
if ($sfRecordType['BusinessProcessId']) {
$businessProcess = $this->config->businessProcesses()
->where('crm_provider_id', $sfRecordType['BusinessProcessId'])
->first();
}
/** @var RecordType $recordType */
$recordType = $this->config->recordTypes()->updateOrCreate([
'crm_provider_id' => $sfRecordType['Id'],
], [
'team_id' => $this->team->id,
'type' => mb_strtolower($sfRecordType['SobjectType']),
'name' => $sfRecordType['Name'],
'is_selectable' => $sfRecordType['IsActive'],
'business_process_id' => $businessProcess->id ?? null,
]);
$this->importRecordTypeFieldValues($recordType);
}
} catch (NoResultsException $noResultsException) {
// Do nothing.
}
}
/**
* Import record type - field value mappings. This only works for standard fields.
*/
public function importRecordTypeFieldValues(RecordType $recordType): void
{
try {
$query = '
SELECT
Metadata
FROM
RecordType
WHERE
Id = :recordTypeId';
$sfFields = $this->queryHandler->metadata($query, [
'recordTypeId' => $recordType->crm_provider_id,
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
// Sync field metadata.
$picklists = $sfField['Metadata']['picklistValues'];
foreach ($picklists as $picklist) {
$field = $this->config->fields()->where([
'type' => Field::TYPE_PICKLIST,
'object_type' => $recordType->type,
'crm_provider_id' => $picklist['picklist'],
])->first();
if ($field) {
$fieldValues = [];
foreach ($picklist['values'] as $value) {
// Must decode: "%2C" becomes "," etc.
$fieldValue = $field->values()
->where('value', urldecode($value['valueName']))
->first();
if ($fieldValue) {
$fieldValues[] = $fieldValue->id;
}
}
$recordType->fieldValues()->sync($fieldValues);
}
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
/**
* @inheritdoc
*/
public function importStages(?array $types = null, ?string $missingStageName = null): ?Stage
{
$params = [];
$missingStage = null;
if ($types === null) {
$types = [Stage::TYPE_LEAD, Stage::TYPE_OPPORTUNITY];
}
foreach ($types as $type) {
if ($type === Stage::TYPE_LEAD) {
$query = '
SELECT
Id, ApiName, MasterLabel, SortOrder
FROM
LeadStatus';
} else {
$query = '
SELECT
Id, ApiName, MasterLabel, IsActive, SortOrder, DefaultProbability
FROM
OpportunityStage';
}
if ($missingStageName) {
$escapedStageName = ValueNormalizer::replaceQueryWithStringLiterals($missingStageName);
$query .= ' WHERE ApiName = :stageName';
$params = [
'stageName' => $escapedStageName,
];
}
try {
$sfStages = $this->queryHandler->query($query, $params);
} catch (NoResultsException $exception) {
$sfStages = [];
}
$missingStage = null;
// Upsert all stages for the team.
foreach ($sfStages as $sfStage) {
$selectable = true;
if (array_key_exists('IsActive', $sfStage)) {
$selectable = $sfStage['IsActive'];
}
$this->restoreAnyTrashedEntity($this->config->stages(), $sfStage['Id']);
$stage = $this->config->stages()->updateOrCreate([
'crm_provider_id' => $sfStage['Id'],
], [
'team_id' => $this->team->id,
'name' => mb_strimwidth($sfStage['ApiName'], 0, 50),
'label' => mb_strimwidth($sfStage['MasterLabel'], 0, 191),
'type' => $type,
'sequence' => $sfStage['SortOrder'] ?? 0,
'is_selectable' => $selectable,
'probability' => $sfStage['DefaultProbability'] ?? null,
]);
if ($missingStageName && $missingStageName === $sfStage['ApiName']) {
$missingStage = $stage;
}
}
if ($missingStageName && $missingStage === null) {
// If they requested a stage that still doesn't exist, it must be inactive so lazy create it.
$missingStage = $this->config->stages()->create([
'crm_provider_id' => Uuid::uuid4(),
'team_id' => $this->team->id,
'name' => mb_strimwidth($missingStageName, 0, 50),
'label' => mb_strimwidth($missingStageName, 0, 191),
'type' => $type,
'sequence' => 0,
'is_selectable' => 0,
]);
}
}
return $missingStage;
}
/**
* @inheritdoc
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
$syncCount = 0;
$fields = $this->getAllFieldsAsArray('lead');
if (\in_array('Id', $fields, true) === false) {
return $syncCount;
}
$query = '
SELECT ' . rtrim(implode(',', $fields), ',') . '
FROM Lead
WHERE LastModifiedDate > :since
ORDER BY LastModifiedDate ASC';
try {
$sfLeads = $this->queryHandler->query($query, [
'since' => $since->format('Y-m-d\TH:i:s\Z'),
]);
foreach ($sfLeads as $sfLead) {
// Only sync if previously imported.
if ($this->hasLead($sfLead['Id'])) {
$this->importLead($sfLead);
$syncCount++;
}
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
$this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::LEAD);
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
$fields = $this->getAllFieldsAsArray('lead');
$sfLead = $this->getRecord('Lead', $crmId, $fields);
return $this->importLead($sfLead);
}
private function importLead($crmData): ?Lead
{
/** @var ?Stage $stage */
$stage = null;
if (isset($crmData['Status'])) {
// Get the current stage.
$stage = $this->config
->stages()
->where('name', $crmData['Status'])
->where('type', Stage::TYPE_LEAD)
->first();
if ($stage === null) {
// Import it.
$stage = $this->importStages([Stage::TYPE_LEAD], $crmData['Status']);
}
}
// If we have no way of importing this, just return null :(
if ($stage === null) {
return null;
}
$countryCode = $crmData['CountryCode'] ?? null;
// Salesforce allows custom "countries" to be created. Disregard these.
if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {
$countryCode = null;
}
// If we have no country code, try to parse it from the country name.
if ($countryCode === null && empty($crmData['Country']) !== false) {
$countryCode = $this->convertCountryNameToCode($crmData['Country']);
}
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($crmData['Phone'] ?? '', 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
$mobilePhone = null;
if (empty($crmData['MobilePhone']) === false) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($crmData['MobilePhone'], 0, 25);
$mobilePhone = phone_e164($countryCode, $number);
}
$convertedDate = null;
$convertedAccount = null;
$convertedOpportunity = null;
$convertedContact = null;
if ($crmData['IsConverted'] == 'true') {
$convertedDate = $crmData['ConvertedDate'];
if (empty($crmData['ConvertedAccountId']) === false) {
$convertedAccount = $this->config
->accounts()
->where('crm_provider_id', $crmData['ConvertedAccountId'])
->first();
if ($convertedAccount === null) {
try {
$convertedAccount = $this->syncAccount($crmData['ConvertedAccountId']);
} catch (HttpNotFoundException $exception) {
// Probably the user has no permissions to access the converted data.
}
}
}
if (empty($crmData['ConvertedOpportunityId']) === false) {
$convertedOpportunity = $this->config
->opportunities()
->where('crm_provider_id', $crmData['ConvertedOpportunityId'])
->first();
if ($convertedOpportunity === null) {
try {
$convertedOpportunity = $this->syncOpportunity($crmData['ConvertedOpportunityId']);
} catch (HttpNotFoundException $exception) {
// Probably the user has no permissions to access the converted data.
}
}
}
if (empty($crmData['ConvertedContactId']) === false) {
$convertedContact = $this->team
->crm
->contacts()
->where('crm_provider_id', $crmData['ConvertedContactId'])
->first();
if ($convertedContact === null) {
try {
$convertedContact = $this->syncContact($crmData['ConvertedContactId']);
} catch (HttpNotFoundException $exception) {
// Probably the user has no permissions to access the converted data.
}
}
}
}
if (empty($crmData['Company'])) {
$company = 'Unknown';
} else {
$company = mb_strimwidth($crmData['Company'], 0, 191);
}
$domain = null;
if (empty($crmData['Website']) === false) {
$domain = mb_strimwidth($crmData['Website'], 0, 191);
$domain = StringUtil::resolveDomain($domain);
}
$createdDate = null;
if (empty($crmData['CreatedDate']) === false) {
$createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');
}
$profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);
$data = [
'team_id' => $this->team->id,
'user_id' => $profile?->user_id,
'owner_id' => $crmData['OwnerId'] ?? '',
'company' => $company,
'domain' => $domain,
'name' => $crmData['Name'] ? mb_strimwidth($crmData['Name'], 0, 191) : '',
'title' => $crmData['Title'] ? mb_strimwidth($crmData['Title'], 0, 128) : null,
'email' => $crmData['Email'] ? mb_strimwidth($crmData['Email'], 0, 80) : null,
'phone' => $parsedNumber['phone'],
'ext' => $parsedNumber['ext'] ?? null,
'mobile_phone' => $mobilePhone,
'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(
crmConfiguration: $this->config,
crmProviderId: $crmData['Id'],
modelType: Lead::class,
fileName: $crmData['Id'],
avatarText: $crmData['Name']
),
'stage_id' => $stage->id,
'record_type_id' => null,
'converted_at' => $convertedDate,
'converted_account_id' => $convertedAccount->id ?? null,
'converted_opportunity_id' => $convertedOpportunity->id ?? null,
'converted_contact_id' => $convertedContact->id ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $createdDate,
];
$this->restoreAnyTrashedEntity($this->config->leads(), $crmData['Id']);
/** @var Lead */
$lead = $this->config->leads()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);
if ($lead->wasChanged('converted_at') && $lead->getConvertedAt() !== null) {
event(new LeadConverted($lead));
}
$this->handleObjectDeletion($lead, $crmData);
return $lead;
}
/**
* @inheritdoc
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
$fields = $this->getAllFieldsAsArray('account');
if (\in_array('Id', $fields, true) === false) {
return $syncCount;
}
$query = '
SELECT ' . rtrim(implode(',', $fields), ',') . '
FROM Account
WHERE LastModifiedDate > :since
ORDER BY LastModifiedDate ASC';
try {
$sfAccounts = $this->queryHandler->query($query, [
'since' => $since->format('Y-m-d\TH:i:s\Z'),
]);
foreach ($sfAccounts as $sfAccount) {
// Only sync if previously imported.
if ($this->hasAccount($sfAccount['Id'])) {
$this->importAccount($sfAccount);
$syncCount++;
}
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
$this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::ACCOUNT);
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
$fields = $this->getAllFieldsAsArray('account');
if (! in_array('Id', $fields, true)) {
$this->logger->info('[Salesforce] Sync account cancelled. Fields are not available.', [
'crmId' => $crmId,
'userId' => $this->profile->getUserId(),
]);
return null;
}
$sfAccount = $this->getRecord('Account', $crmId, $fields);
return $this->importAccount($sfAccount);
}
private function importAccount($crmData): Account
{
$countryCode = $crmData['BillingCountryCode'] ?? $crmData['ShippingCountryCode'] ?? null;
// Salesforce allows custom "countries" to be created. Disregard these.
if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {
$countryCode = null;
}
// If we have no country code, try to parse it from the country names.
if ($countryCode === null && empty($crmData['BillingCountry']) === false) {
$countryCode = $this->convertCountryNameToCode($crmData['BillingCountry']);
}
if ($countryCode === null && empty($crmData['ShippingCountry']) === false) {
$countryCode = $this->convertCountryNameToCode($crmData['ShippingCountry']);
}
if (empty($crmData['Phone']) === false) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($crmData['Phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$industry = null;
if (empty($crmData['Industry']) === false) {
$industry = mb_strimwidth($crmData['Industry'], 0, 40);
}
$domain = null;
if (empty($crmData['Website']) === false) {
$domain = mb_strimwidth($crmData['Website'], 0, 191);
$domain = StringUtil::resolveDomain($domain);
}
$createdDate = null;
if (empty($crmData['CreatedDate']) === false) {
$createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');
}
$profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);
$data = [
'team_id' => $this->team->id,
'user_id' => $profile?->user_id,
'owner_id' => $crmData['OwnerId'],
'name' => mb_strimwidth($crmData['Name'], 0, 191),
'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(
crmConfiguration: $this->config,
crmProviderId: $crmData['Id'],
modelType: Account::class,
fileName: $crmData['Id'],
avatarText: $crmData['Name']
),
'industry' => $industry,
'domain' => $domain,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $createdDate,
];
$this->restoreAnyTrashedEntity($this->config->accounts(), $crmData['Id']);
/** @var Account */
$account = $this->config->accounts()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);
$this->handleObjectDeletion($account, $crmData);
return $account;
}
/**
* @inheritdoc
*/
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$syncCount = 0;
$logParams = $parameters;
$parameters['profile'] = $this->profile;
$logParams['user'] = $this->profile->getUserId();
if (count($strategies) > 1) {
$this->logger->warning('[' . $this->getDisplayName() . '] Multiple sync strategies used', [
'teamId' => $this->team->getUuid(),
'params' => $logParams,
'strategies_count' => count($strategies),
]);
}
foreach ($strategies as $syncStrategy) {
$name = $syncStrategy->getStrategyName();
try {
$sfOpportunities = $syncStrategy->fetchOpportunities($parameters);
$totalRecords = $sfOpportunities->count();
foreach ($sfOpportunities as $sfOpportunity) {
$this->importOpportunity($sfOpportunity);
$syncCount++;
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
$this->logger->warning('[' . $this->getDisplayName() . '] No opportunities found', [
'teamId' => $this->team->getUuid(),
'name' => $name,
'params' => $logParams,
'reason' => $noResultsException->getMessage(),
]);
} catch (CrmException $crmException) {
// Nothing to sync.
$this->logger->warning('[' . $this->getDisplayName() . '] Opportunity sync failed', [
'teamId' => $this->team->getUuid(),
'name' => $name,
'params' => $logParams,
'reason' => $crmException->getMessage(),
]);
}
}
$this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::OPPORTUNITY, ['params' => $logParams]);
// debug to see how if count of opportunities reaches 1000
if ($syncCount >= 1000) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Sync Opportunities - count warning',
[
'team_id' => $this->team->getId(),
'params' => $logParams,
'count' => $syncCount,
'strategies_count' => count($strategies),
'total_records' => $totalRecords ?? null,
]
);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY
);
$parameters = [
'profile' => $this->profile,
'crm_id' => $crmId,
];
try {
$sfOpportunity = $strategy->fetchOpportunities($parameters);
} catch (HttpNotFoundException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->id_string,
'crmId' => $crmId,
]);
return null;
} catch (CrmException $crmException) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity sync failed', [
'teamId' => $this->team->id_string,
'crmId' => $crmId,
'exception' => $crmException->getMessage(),
]);
return null;
}
if ($sfOpportunity instanceof ArrayIterator) {
return $this->importOpportunity($sfOpportunity->getItems());
}
return $this->importOpportunity($sfOpportunity);
}
private function importOpportunity($crmData): ?Opportunity
{
/** @var ?Stage $stage */
$stage = null;
if (isset($crmData['StageName'])) {
$stage = $this->config
->stages()
->where('name', $crmData['StageName'])
->where('type', Stage::TYPE_OPPORTUNITY)
->orderBy('is_selectable', 'DESC')
->orderBy('id')
->first();
if ($stage === null) {
// Import it.
$stage = $this->importStages([Stage::TYPE_OPPORTUNITY], $crmData['StageName']);
}
}
$recordType = null;
if (empty($crmData['RecordTypeId']) === false) {
/** @var ?RecordType $recordType */
$recordType = $this->config->recordTypes()
->where('crm_provider_id', $crmData['RecordTypeId'])
...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"\"LeadConverted\" not found, press ⌘G to search from the top","depth":2,"bounds":{"left":0.2599734,"top":0.5211492,"width":0.12533244,"height":0.013567438},"value":"\"LeadConverted\" not found, press ⌘G to search from the top","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"bounds":{"left":0.2599734,"top":0.5211492,"width":0.12533244,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"master, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.040226065,"height":0.025538707},"help_text":"Git Branch: master<br/>Some incoming commits are not fetched<br/>","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Replace Field","depth":4,"bounds":{"left":0.12101064,"top":0.17956904,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Search History","depth":3,"bounds":{"left":0.13364361,"top":0.17877094,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"LeadConverted","depth":4,"bounds":{"left":0.14461437,"top":0.17877094,"width":0.06881649,"height":0.015961692},"value":"LeadConverted","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.22240691,"top":0.17877094,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Match Case","depth":3,"bounds":{"left":0.23238032,"top":0.17877094,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Words","depth":3,"bounds":{"left":0.24102394,"top":0.17877094,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Regex","depth":3,"bounds":{"left":0.24966756,"top":0.17877094,"width":0.00731383,"height":0.017557861},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Replace History","depth":3,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Replace","depth":4,"role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"New Line","depth":3,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Preserve case","depth":3,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"4/4","depth":4,"bounds":{"left":0.2632979,"top":0.17797287,"width":0.025598405,"height":0.017557861},"role_description":"text"},{"role":"AXButton","text":"Previous Occurrence","depth":4,"bounds":{"left":0.28889626,"top":0.17717478,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Occurrence","depth":4,"bounds":{"left":0.2975399,"top":0.17717478,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Filter Search Results","depth":4,"bounds":{"left":0.30618352,"top":0.17717478,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open in Window, Multiple Cursors","depth":4,"bounds":{"left":0.3148271,"top":0.17717478,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Click to highlight","depth":4,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Close","depth":4,"bounds":{"left":0.5119681,"top":0.17717478,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.45412233,"top":0.20830008,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"144","depth":4,"bounds":{"left":0.46575797,"top":0.20830008,"width":0.011968086,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.47972074,"top":0.20830008,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.48969415,"top":0.20830008,"width":0.009973404,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.50166225,"top":0.20830008,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.5106383,"top":0.20670392,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.51795214,"top":0.20670392,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Crm\\Salesforce;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Component\\Country\\CountriesMap;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Services\\Crm\\FetchRelatedActivityInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\LayoutManagementInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\MatchCrmEntitiesInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\Provider\\SalesforceBatchSyncInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\Provider\\SalesforceInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\RemoteEntityLookupInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\RemoteEntityManipulationInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\RemoteNoteEntityManipulationInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SearchTaskInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SendSummaryToCrmInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SettingsInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SupportsObjectTypeParseInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SyncCrmEntitiesInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SyncCrmProfileRecordTypesInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\VerifyTaskExistsInterface;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Activities\\Crm\\LeadConverted;\nuse Jiminny\\Events\\Activities\\Crm\\ActivityLeadConverted;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\HttpBadRequestException;\nuse Jiminny\\Exceptions\\HttpNotFoundException;\nuse Jiminny\\Exceptions\\NoResultsException;\nuse Jiminny\\Exceptions\\ServiceUnavailableException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Calendar\\CalendarEvent;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Contracts\\ActivityContract;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\ContactRole;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Crm\\RecordType;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\TeamSettings;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\Crm\\ContactRoleRepository;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\Crm\\ProfileRepository;\nuse Jiminny\\Repositories\\Crm\\RecordTypeFieldValuesRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\BaseService;\nuse Jiminny\\Services\\Crm\\Helpers\\ArrayIterator;\nuse Jiminny\\Services\\Crm\\MatchDomainByEmailInterface;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Services\\Crm\\ResolveCompanyNameByEmailTrait;\nuse Jiminny\\Services\\Crm\\Salesforce\\Fields\\FieldTypeConverter;\nuse Jiminny\\Services\\Crm\\Salesforce\\Fields\\ValueNormalizer;\nuse Jiminny\\Services\\Crm\\Salesforce\\ServiceTraits\\RecordManipulationsTrait;\nuse Jiminny\\Services\\Crm\\Salesforce\\ServiceTraits\\SyncFieldsTrait;\nuse Jiminny\\Utils\\CurrencyFormatter;\nuse Jiminny\\Utils\\StringUtil;\nuse Ramsey\\Uuid\\Uuid;\nuse Sentry\\Laravel\\Facade as Sentry;\n\nclass Service extends BaseService implements\n SalesforceInterface,\n SalesforceBatchSyncInterface,\n SyncCrmEntitiesInterface,\n SyncCrmProfileRecordTypesInterface,\n RemoteEntityManipulationInterface,\n FetchRelatedActivityInterface,\n SendSummaryToCrmInterface,\n MatchDomainByEmailInterface,\n SearchTaskInterface,\n LayoutManagementInterface,\n SettingsInterface,\n MatchCrmEntitiesInterface,\n RemoteEntityLookupInterface,\n SupportsObjectTypeParseInterface,\n RemoteNoteEntityManipulationInterface,\n VerifyTaskExistsInterface\n{\n use ResolveCompanyNameByEmailTrait;\n use SyncFieldsTrait;\n use DeleteObjectsTrait;\n use RecordManipulationsTrait;\n use ServiceTraits\\BatchSyncTrait;\n\n /**\n * Note Body Limit for the Old Note-Taking Tool\n *\n * @var int\n */\n private const int CLASSIC_NOTE_MAX_LENGTH = 32000;\n\n /**\n * Note Content Limit for the New Notes\n *\n * @var int\n */\n private const int ENHANCED_NOTE_MAX_LENGTH = 50000000;\n\n private const string INSTALLED_PACKAGE_ID = '033Tw0000007bKbIAI';\n\n private const int CACHE_TTL = 600;\n\n private const int TASK_VERIFICATION_CACHE_TTL = 86400; // 1 day - 86400\n\n /**\n * @var Client\n */\n protected $client;\n\n private PayloadBuilder $payloadBuilder;\n private QueryHandler $queryHandler;\n\n private OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n\n public function __construct(\n Client $client,\n PayloadBuilder $payloadBuilder,\n private readonly CountriesMap $countriesMap,\n private readonly ProspectPhotoPathService $prospectPhotoPathService,\n ) {\n parent::__construct();\n\n $this->client = $client;\n $this->payloadBuilder = $payloadBuilder;\n $this->queryHandler = app(QueryHandler::class, [\n 'client' => $this->client,\n 'logger' => $this->logger,\n ]);\n $this->opportunitySyncStrategyResolver = app(OpportunitySyncStrategyResolver::class, [\n 'client' => $this->client,\n ]);\n }\n\n public function getDisplayName(): string\n {\n return 'Salesforce';\n }\n\n public function getJobDelay(): int\n {\n return 1;\n }\n\n protected function getOAuthAccount(User $user): ?SocialAccount\n {\n return $user->getSocialAccount(SocialAccount::PROVIDER_SALESFORCE);\n }\n\n public function verifyTaskExists(Activity $activity): bool\n {\n $crmProviderId = $activity->getCrmProviderId();\n $cacheKey = \"crm_task_exists:{$this->config->getId()}:$crmProviderId\";\n\n return Cache::remember($cacheKey, self::TASK_VERIFICATION_CACHE_TTL, function () use ($activity, $crmProviderId) {\n $playbook = $this->getPlaybookFromActivity($activity);\n\n if ($playbook === null) {\n $this->logger->warning('[Salesforce] Cannot verify task - no playbook found', [\n 'activity' => $activity->getId(),\n 'crm_provider_id' => $crmProviderId,\n ]);\n\n return false;\n }\n\n $objectType = $playbook->getActivityType() === Playbook::ACTIVITY_TYPE_EVENT ? 'Event' : 'Task';\n\n try {\n $record = $this->getRecord($objectType, $crmProviderId, ['Id', 'IsDeleted']);\n\n return ! empty($record) && ($record['IsDeleted'] ?? false) === false;\n } catch (HttpNotFoundException|HttpBadRequestException) {\n $this->logger->info('[Salesforce] Activity record not found during verification', [\n 'activity' => $activity->getId(),\n 'object_type' => $objectType,\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return false;\n }\n });\n }\n\n public function query(string $queryToRun, array $parameters = []): QueryIterator\n {\n // Due to poorly designed external calls, this method cannot be entirely removed\n return $this->queryHandler->query($queryToRun, $parameters);\n }\n\n /*=========== Organization Information ===============*/\n\n /**\n * Get a list of all the API Versions for the instance.\n *\n * @throws CrmException\n *\n * @return mixed\n *\n */\n public function getApiVersions()\n {\n $url = $this->config->crm_base_url . '/services/data';\n\n $response = $this->client->get($url);\n\n return json_decode($response->getBody(), true);\n }\n\n\n /**\n * Gets the valid recordTypes for a given Salesforce Object via the describe API.\n *\n * @param string $crmObject The name of the Salesforce object. i.e. Account or Contact\n *\n * @return array The API output, converted from JSON to an associative array.\n */\n public function getRecordTypes(string $crmObject): array\n {\n $url = $this->client->getObjectsUrl() . $crmObject . '/describe';\n\n $response = $this->client->get($url);\n $jsonResponse = json_decode($response->getBody(), true);\n\n $fields = [];\n foreach ($jsonResponse['recordTypeInfos'] as $row) {\n $fields[] = ['recordTypeId' => $row['recordTypeId'], 'default' => $row['defaultRecordTypeMapping']];\n }\n\n return $fields;\n }\n\n /**\n * Convert raw field data into a format compatible with CRM APIs.\n */\n public function normalizeValue(string $fieldType, string $fieldValue, bool $internal = false): string\n {\n return ValueNormalizer::normalize($fieldType, $fieldValue, $internal);\n }\n\n /**\n * @inheritdoc\n */\n public function getDefaultFields(string $activityType): array\n {\n $fields = [];\n\n $defaultFields = ($activityType === Playbook::ACTIVITY_TYPE_TASK)\n ? FieldDefinitions::defaultTaskFields()\n : FieldDefinitions::defaultEventFields();\n\n // This lazy creates these fields if not already setup.\n foreach ($defaultFields as $defaultField) {\n $fields[] = $this->config->fields()->firstOrCreate($defaultField);\n }\n\n return $fields;\n }\n\n /**\n * @inheritdoc\n */\n public function getDefaultActivityField(string $activityType): Field\n {\n // Setup the activity field as the default Type.\n /** @var Field $activityField */\n $activityField = $this->config->fields()->where([\n 'crm_provider_id' => 'Type',\n 'object_type' => $activityType,\n ])->first();\n\n return $activityField;\n }\n\n /**\n * @inheritdoc\n */\n public function getSupportedPlaybookTypes(): array\n {\n return [Playbook::ACTIVITY_TYPE_TASK, Playbook::ACTIVITY_TYPE_EVENT];\n }\n\n protected function getDefaultFollowupLayoutFields(string $activityType): array\n {\n $fields = [];\n $fieldRepo = app(FieldRepository::class);\n\n $fieldFilter = ($activityType === Playbook::ACTIVITY_TYPE_TASK)\n ? FieldDefinitions::taskFollowupFieldsFilter()\n : FieldDefinitions::eventFollowupFieldsFilter();\n\n foreach ($fieldFilter as $eachFilter) {\n $field = $fieldRepo->findOneConfigurationFieldByProperties($this->config, $eachFilter);\n\n // Only add the field if it is created, which it should be.\n if ($field) {\n $fields[] = $field;\n }\n }\n\n return $fields;\n }\n\n public function getDealInsightsFields(): array\n {\n return FieldDefinitions::dealInsightsFields();\n }\n\n private function isCustomField(Field $field): bool\n {\n return substr($field->crm_provider_id, -\\strlen('__c')) === '__c';\n }\n\n /**\n * This one is now called only when ImportActivityTypes is triggered or SyncFieldMetadata executed manually\n * Regular sync now uses SharedSyncFieldsTrait -> syncSingleObjectType\n * Needs to be replaced later on\n */\n public function syncField(Field $field): void\n {\n try {\n if ($this->isCustomField($field)) {\n $query = '\n SELECT\n Id, Metadata, TableEnumOrId\n FROM\n CustomField\n WHERE\n DeveloperName = :fieldName\n AND\n TableEnumOrId = :fieldType\n AND\n NamespacePrefix = :namespacePrefix';\n\n // We need to constrain the field lookup to the object, in case it's used in multiple places.\n $objectType = \\in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true)\n ? 'activity'\n : $field->object_type;\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'fieldName' => substr($field->crm_provider_id, 0, -\\strlen('__c')),\n 'fieldType' => ucfirst($objectType),\n\n // This is used to ensure we only consider the field within the org, not installed packages.\n 'namespacePrefix' => 'null',\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n // Sync field metadata.\n $metadata = $sfField['Metadata'];\n\n $field->description = mb_strimwidth($metadata['description'] ?? '', 0, 191);\n $field->label = mb_strimwidth($metadata['label'] ?? '', 0, Field::LABEL_MAX_LENGTH);\n $field->type = $this->convertFieldType($metadata['type'], $field->getEntityName());\n $field->is_mandatory = ($metadata['required'] === true);\n $field->length = $metadata['length'];\n $field->default_value = mb_strimwidth(trim($metadata['defaultValue'] ?? '', '\"'), 0, 191);\n $field->save();\n } else {\n $query = '\n SELECT\n Id, DataType, DeveloperName, Label, Length, Description\n FROM\n FieldDefinition\n WHERE\n DurableId = :entityName';\n\n $entityName = $field->getEntityName();\n $sfFields = $this->queryHandler->metadata($query, [\n 'entityName' => $entityName,\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n $convertedType = $this->convertFieldType($sfField['DataType'], $entityName);\n $label = mb_strimwidth($sfField['Label'], 0, Field::LABEL_MAX_LENGTH);\n\n if ($field->isBusinessType()) {\n $label = 'Opportunity Type';\n }\n\n $field->description = mb_strimwidth($sfField['Description'], 0, Field::DESCRIPTION_MAX_LENGTH);\n $field->label = $label;\n $field->type = $convertedType;\n $field->length = $sfField['Length'];\n $field->save();\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n private function convertFieldType(string $from, ?string $entityName = null): string\n {\n $converter = new FieldTypeConverter();\n\n return $converter->convert($from, $entityName);\n }\n\n /**\n * @inheritdoc\n */\n public function importPicklistValues(Field $field): array\n {\n $values = [];\n $fieldValues = [];\n\n try {\n if ($this->isCustomField($field)) {\n $query = '\n SELECT\n Id, Metadata, TableEnumOrId\n FROM\n CustomField\n WHERE\n DeveloperName = :fieldName\n AND\n TableEnumOrId = :fieldType\n AND\n NamespacePrefix = :namespacePrefix';\n\n // We need to constrain the field lookup to the object, in case it's used in multiple places.\n $objectType = \\in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true) ?\n 'activity' : $field->object_type;\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'fieldName' => substr($field->crm_provider_id, 0, -\\strlen('__c')),\n 'fieldType' => ucfirst($objectType),\n // This is used to ensure we only consider the field within the org, not installed packages.\n 'namespacePrefix' => 'null',\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n $valueSet = $sfField['Metadata']['valueSet'];\n\n if ($valueSet['valueSetName'] === null) {\n // Local picklist values can be obtained easily.\n $picklistValues = $valueSet['valueSetDefinition']['value'];\n } else {\n // But for some fields, we just get the Global Value Picklist pointer so need to do more work.\n $picklistValues = $this->importGlobalValuePicklistValues($valueSet['valueSetName']);\n }\n\n // Import all active values.\n foreach ($picklistValues as $i => $sfFieldValue) {\n // Setup default value.\n if ($sfFieldValue['default']) {\n $field->update(['default_value' => $sfFieldValue['valueName']]);\n }\n\n // This comes through as null if active (lol).\n if ($sfFieldValue['isActive'] !== false) {\n $values[] = [\n 'value' => $sfFieldValue['valueName'],\n 'label' => $sfFieldValue['valueName'],\n 'sequence' => $i,\n 'is_default' => $sfFieldValue['default'],\n ];\n }\n }\n } else {\n $objectFields = $this->getObjectFields($field->object_type);\n $fieldId = $field->crm_provider_id;\n\n // Only work with our field of interest.\n $objectField = array_filter($objectFields, function ($item) use ($fieldId) {\n return $item['name'] === $fieldId;\n });\n\n $objectField = array_shift($objectField);\n if (empty($objectField['picklistValues']) === false) {\n foreach ($objectField['picklistValues'] as $i => $sfFieldValue) {\n // Skip inactive values.\n if ($sfFieldValue['active'] === false) {\n continue;\n }\n\n // Setup default value.\n if ($sfFieldValue['defaultValue']) {\n $field->update(['default_value' => $sfFieldValue['value']]);\n }\n\n $values[] = [\n 'value' => $sfFieldValue['value'],\n 'label' => $sfFieldValue['label'],\n 'sequence' => $i,\n 'is_default' => $sfFieldValue['defaultValue'],\n ];\n }\n }\n }\n\n $fieldsToPurge = $field->values()->get()->pluck('value')->toArray();\n\n foreach ($values as $value) {\n $value['value'] = substr($value['value'] ?? '', 0, 255);\n $fieldValues[] = $field->values()->updateOrCreate([\n 'value' => $value['value'],\n ], $value);\n\n // Remove this value from the ones we are going to purge.\n if (($key = array_search($value['value'], $fieldsToPurge, true)) !== false) {\n unset($fieldsToPurge[$key]);\n }\n }\n\n // Delete the old values that are no longer used.\n // Get IDs of the values to be deleted\n $valuesToDelete = $field->values()->whereIn('value', $fieldsToPurge);\n $valuesToDeleteIds = $valuesToDelete->pluck('id');\n if (! $valuesToDeleteIds->isEmpty()) {\n $recordTypeFieldValuesRepository = app(RecordTypeFieldValuesRepository::class);\n $recordTypeFieldValuesRepository->deleteForCrmFieldValueIds($valuesToDeleteIds->toArray());\n\n // Now safely delete from crm_field_values\n $valuesToDelete->delete();\n }\n\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n return $fieldValues;\n }\n\n /**\n * Gets values from Global Value Picklists.\n */\n private function importGlobalValuePicklistValues(string $picklistName): array\n {\n $query = '\n SELECT\n Metadata\n FROM\n GlobalValueSet\n WHERE\n DeveloperName = :picklistName\n LIMIT 1';\n\n try {\n $sfValues = $this->queryHandler->metadata($query, [\n 'picklistName' => $picklistName,\n ]);\n\n // There is always 1 result at this point.\n $sfValue = $sfValues->current();\n\n return $sfValue['Metadata']['customValue'];\n } catch (NoResultsException $noResultsException) {\n // Nothing returned.\n\n return [];\n }\n }\n\n /**\n * @inheritdoc\n */\n public function syncProfileRecordTypes(): void\n {\n $objectTypes = [\n 'lead',\n 'account',\n 'contact',\n 'opportunity',\n 'task',\n 'event',\n ];\n\n foreach ($objectTypes as $objectType) {\n try {\n $crmRecordTypes = $this->getRecordTypes(ucfirst($objectType));\n\n foreach ($crmRecordTypes as $crmRecordType) {\n // If the record type is default and not the Master type, set this.\n if ($crmRecordType['default'] && $crmRecordType['recordTypeId'] !== '012000000000000AAA') {\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $crmRecordType['recordTypeId'])\n ->first();\n\n if ($recordType) {\n $this->profile->{$objectType . '_record_type_id'} = $recordType->id;\n }\n }\n }\n } catch (HttpNotFoundException $exception) {\n Log::error('No access to ' . $objectType . ' object, skipping...');\n\n // XXX: should we log this fact somewhere?\n continue;\n }\n }\n\n if ($this->profile->isDirty()) {\n $this->profile->save();\n }\n }\n\n /**\n * Gets business processes.\n */\n public function importBusinessProcesses(): void\n {\n $query = '\n SELECT\n Id, IsActive, Name, TableEnumOrId\n FROM\n BusinessProcess\n WHERE\n TableEnumOrId IN (\\'Lead\\',\\'Opportunity\\')';\n\n try {\n $sfProcesses = $this->queryHandler->query($query);\n\n // Upsert all processes for the team.\n foreach ($sfProcesses as $sfProcess) {\n /** @var BusinessProcess $businessProcess */\n $businessProcess = $this->config->businessProcesses()->updateOrCreate([\n 'crm_provider_id' => $sfProcess['Id'],\n ], [\n 'team_id' => $this->team->id,\n 'name' => $sfProcess['Name'],\n 'type' => $sfProcess['TableEnumOrId'] === 'Lead' ? 'lead' : 'opportunity',\n 'is_selectable' => $sfProcess['IsActive'],\n ]);\n\n $this->importBusinessProcessStages($businessProcess);\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n /**\n * Gets business process stages.\n */\n public function importBusinessProcessStages(BusinessProcess $businessProcess): void\n {\n $query = '\n SELECT\n Metadata\n FROM\n BusinessProcess\n WHERE\n Id = :processId';\n\n try {\n $stages = [];\n $sfProcessStages = $this->queryHandler->metadata($query, [\n 'processId' => $businessProcess->crm_provider_id,\n ]);\n\n // There is always 1 result at this point.\n $sfProcessStage = $sfProcessStages->current();\n\n // Upsert all processes for the team.\n foreach ($sfProcessStage['Metadata']['values'] as $sfProcessStage) {\n $sanitizedName = urldecode($sfProcessStage['valueName']); // Must decode: \"%2C\" becomes \",\" etc.\n\n $stage = $businessProcess->crm->stages()\n // This MUST match on label because this API doesn't use API Name.\n ->where('label', $sanitizedName)\n ->where('type', $businessProcess->type)\n ->where('is_selectable', 1)\n ->first();\n\n if ($stage) {\n $stages[] = $stage->id;\n }\n }\n\n $businessProcess->stages()->sync($stages);\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n /**\n * Gets record types.\n */\n public function importRecordTypes(): void\n {\n $query = '\n SELECT\n Id, IsActive, Name, BusinessProcessId, SobjectType\n FROM\n RecordType';\n\n try {\n $sfRecordTypes = $this->queryHandler->query($query);\n\n // Upsert all record types for the process.\n foreach ($sfRecordTypes as $sfRecordType) {\n $businessProcess = null;\n if ($sfRecordType['BusinessProcessId']) {\n $businessProcess = $this->config->businessProcesses()\n ->where('crm_provider_id', $sfRecordType['BusinessProcessId'])\n ->first();\n }\n\n /** @var RecordType $recordType */\n $recordType = $this->config->recordTypes()->updateOrCreate([\n 'crm_provider_id' => $sfRecordType['Id'],\n ], [\n 'team_id' => $this->team->id,\n 'type' => mb_strtolower($sfRecordType['SobjectType']),\n 'name' => $sfRecordType['Name'],\n 'is_selectable' => $sfRecordType['IsActive'],\n 'business_process_id' => $businessProcess->id ?? null,\n ]);\n\n $this->importRecordTypeFieldValues($recordType);\n }\n } catch (NoResultsException $noResultsException) {\n // Do nothing.\n }\n }\n\n /**\n * Import record type - field value mappings. This only works for standard fields.\n */\n public function importRecordTypeFieldValues(RecordType $recordType): void\n {\n try {\n $query = '\n SELECT\n Metadata\n FROM\n RecordType\n WHERE\n Id = :recordTypeId';\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'recordTypeId' => $recordType->crm_provider_id,\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n // Sync field metadata.\n $picklists = $sfField['Metadata']['picklistValues'];\n\n foreach ($picklists as $picklist) {\n $field = $this->config->fields()->where([\n 'type' => Field::TYPE_PICKLIST,\n 'object_type' => $recordType->type,\n 'crm_provider_id' => $picklist['picklist'],\n ])->first();\n\n if ($field) {\n $fieldValues = [];\n\n foreach ($picklist['values'] as $value) {\n // Must decode: \"%2C\" becomes \",\" etc.\n $fieldValue = $field->values()\n ->where('value', urldecode($value['valueName']))\n ->first();\n\n if ($fieldValue) {\n $fieldValues[] = $fieldValue->id;\n }\n }\n\n $recordType->fieldValues()->sync($fieldValues);\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n /**\n * @inheritdoc\n */\n public function importStages(?array $types = null, ?string $missingStageName = null): ?Stage\n {\n $params = [];\n $missingStage = null;\n if ($types === null) {\n $types = [Stage::TYPE_LEAD, Stage::TYPE_OPPORTUNITY];\n }\n\n foreach ($types as $type) {\n if ($type === Stage::TYPE_LEAD) {\n $query = '\n SELECT\n Id, ApiName, MasterLabel, SortOrder\n FROM\n LeadStatus';\n } else {\n $query = '\n SELECT\n Id, ApiName, MasterLabel, IsActive, SortOrder, DefaultProbability\n FROM\n OpportunityStage';\n }\n\n if ($missingStageName) {\n $escapedStageName = ValueNormalizer::replaceQueryWithStringLiterals($missingStageName);\n\n $query .= ' WHERE ApiName = :stageName';\n\n $params = [\n 'stageName' => $escapedStageName,\n ];\n }\n\n try {\n $sfStages = $this->queryHandler->query($query, $params);\n } catch (NoResultsException $exception) {\n $sfStages = [];\n }\n\n $missingStage = null;\n\n // Upsert all stages for the team.\n foreach ($sfStages as $sfStage) {\n $selectable = true;\n if (array_key_exists('IsActive', $sfStage)) {\n $selectable = $sfStage['IsActive'];\n }\n\n $this->restoreAnyTrashedEntity($this->config->stages(), $sfStage['Id']);\n\n $stage = $this->config->stages()->updateOrCreate([\n 'crm_provider_id' => $sfStage['Id'],\n ], [\n 'team_id' => $this->team->id,\n 'name' => mb_strimwidth($sfStage['ApiName'], 0, 50),\n 'label' => mb_strimwidth($sfStage['MasterLabel'], 0, 191),\n 'type' => $type,\n 'sequence' => $sfStage['SortOrder'] ?? 0,\n 'is_selectable' => $selectable,\n 'probability' => $sfStage['DefaultProbability'] ?? null,\n ]);\n\n if ($missingStageName && $missingStageName === $sfStage['ApiName']) {\n $missingStage = $stage;\n }\n }\n\n if ($missingStageName && $missingStage === null) {\n // If they requested a stage that still doesn't exist, it must be inactive so lazy create it.\n $missingStage = $this->config->stages()->create([\n 'crm_provider_id' => Uuid::uuid4(),\n 'team_id' => $this->team->id,\n 'name' => mb_strimwidth($missingStageName, 0, 50),\n 'label' => mb_strimwidth($missingStageName, 0, 191),\n 'type' => $type,\n 'sequence' => 0,\n 'is_selectable' => 0,\n ]);\n }\n }\n\n return $missingStage;\n }\n\n /**\n * @inheritdoc\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n $syncCount = 0;\n $fields = $this->getAllFieldsAsArray('lead');\n if (\\in_array('Id', $fields, true) === false) {\n return $syncCount;\n }\n\n $query = '\n SELECT ' . rtrim(implode(',', $fields), ',') . '\n FROM Lead\n WHERE LastModifiedDate > :since\n ORDER BY LastModifiedDate ASC';\n\n try {\n $sfLeads = $this->queryHandler->query($query, [\n 'since' => $since->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($sfLeads as $sfLead) {\n // Only sync if previously imported.\n if ($this->hasLead($sfLead['Id'])) {\n $this->importLead($sfLead);\n $syncCount++;\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::LEAD);\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n $fields = $this->getAllFieldsAsArray('lead');\n\n $sfLead = $this->getRecord('Lead', $crmId, $fields);\n\n return $this->importLead($sfLead);\n }\n\n private function importLead($crmData): ?Lead\n {\n /** @var ?Stage $stage */\n $stage = null;\n if (isset($crmData['Status'])) {\n // Get the current stage.\n $stage = $this->config\n ->stages()\n ->where('name', $crmData['Status'])\n ->where('type', Stage::TYPE_LEAD)\n ->first();\n\n if ($stage === null) {\n // Import it.\n $stage = $this->importStages([Stage::TYPE_LEAD], $crmData['Status']);\n }\n }\n\n // If we have no way of importing this, just return null :(\n if ($stage === null) {\n return null;\n }\n\n $countryCode = $crmData['CountryCode'] ?? null;\n\n // Salesforce allows custom \"countries\" to be created. Disregard these.\n if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {\n $countryCode = null;\n }\n\n // If we have no country code, try to parse it from the country name.\n if ($countryCode === null && empty($crmData['Country']) !== false) {\n $countryCode = $this->convertCountryNameToCode($crmData['Country']);\n }\n\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($crmData['Phone'] ?? '', 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n\n $mobilePhone = null;\n if (empty($crmData['MobilePhone']) === false) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($crmData['MobilePhone'], 0, 25);\n $mobilePhone = phone_e164($countryCode, $number);\n }\n\n $convertedDate = null;\n $convertedAccount = null;\n $convertedOpportunity = null;\n $convertedContact = null;\n\n if ($crmData['IsConverted'] == 'true') {\n $convertedDate = $crmData['ConvertedDate'];\n\n if (empty($crmData['ConvertedAccountId']) === false) {\n $convertedAccount = $this->config\n ->accounts()\n ->where('crm_provider_id', $crmData['ConvertedAccountId'])\n ->first();\n\n if ($convertedAccount === null) {\n try {\n $convertedAccount = $this->syncAccount($crmData['ConvertedAccountId']);\n } catch (HttpNotFoundException $exception) {\n // Probably the user has no permissions to access the converted data.\n }\n }\n }\n\n if (empty($crmData['ConvertedOpportunityId']) === false) {\n $convertedOpportunity = $this->config\n ->opportunities()\n ->where('crm_provider_id', $crmData['ConvertedOpportunityId'])\n ->first();\n\n if ($convertedOpportunity === null) {\n try {\n $convertedOpportunity = $this->syncOpportunity($crmData['ConvertedOpportunityId']);\n } catch (HttpNotFoundException $exception) {\n // Probably the user has no permissions to access the converted data.\n }\n }\n }\n\n if (empty($crmData['ConvertedContactId']) === false) {\n $convertedContact = $this->team\n ->crm\n ->contacts()\n ->where('crm_provider_id', $crmData['ConvertedContactId'])\n ->first();\n\n if ($convertedContact === null) {\n try {\n $convertedContact = $this->syncContact($crmData['ConvertedContactId']);\n } catch (HttpNotFoundException $exception) {\n // Probably the user has no permissions to access the converted data.\n }\n }\n }\n }\n\n if (empty($crmData['Company'])) {\n $company = 'Unknown';\n } else {\n $company = mb_strimwidth($crmData['Company'], 0, 191);\n }\n\n $domain = null;\n if (empty($crmData['Website']) === false) {\n $domain = mb_strimwidth($crmData['Website'], 0, 191);\n $domain = StringUtil::resolveDomain($domain);\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $data = [\n 'team_id' => $this->team->id,\n 'user_id' => $profile?->user_id,\n 'owner_id' => $crmData['OwnerId'] ?? '',\n 'company' => $company,\n 'domain' => $domain,\n 'name' => $crmData['Name'] ? mb_strimwidth($crmData['Name'], 0, 191) : '',\n 'title' => $crmData['Title'] ? mb_strimwidth($crmData['Title'], 0, 128) : null,\n 'email' => $crmData['Email'] ? mb_strimwidth($crmData['Email'], 0, 80) : null,\n 'phone' => $parsedNumber['phone'],\n 'ext' => $parsedNumber['ext'] ?? null,\n 'mobile_phone' => $mobilePhone,\n 'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n crmConfiguration: $this->config,\n crmProviderId: $crmData['Id'],\n modelType: Lead::class,\n fileName: $crmData['Id'],\n avatarText: $crmData['Name']\n ),\n 'stage_id' => $stage->id,\n 'record_type_id' => null,\n 'converted_at' => $convertedDate,\n 'converted_account_id' => $convertedAccount->id ?? null,\n 'converted_opportunity_id' => $convertedOpportunity->id ?? null,\n 'converted_contact_id' => $convertedContact->id ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $createdDate,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->leads(), $crmData['Id']);\n\n /** @var Lead */\n $lead = $this->config->leads()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n if ($lead->wasChanged('converted_at') && $lead->getConvertedAt() !== null) {\n event(new LeadConverted($lead));\n }\n\n $this->handleObjectDeletion($lead, $crmData);\n\n return $lead;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n $fields = $this->getAllFieldsAsArray('account');\n\n if (\\in_array('Id', $fields, true) === false) {\n return $syncCount;\n }\n\n $query = '\n SELECT ' . rtrim(implode(',', $fields), ',') . '\n FROM Account\n WHERE LastModifiedDate > :since\n ORDER BY LastModifiedDate ASC';\n\n try {\n $sfAccounts = $this->queryHandler->query($query, [\n 'since' => $since->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($sfAccounts as $sfAccount) {\n // Only sync if previously imported.\n if ($this->hasAccount($sfAccount['Id'])) {\n $this->importAccount($sfAccount);\n $syncCount++;\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::ACCOUNT);\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n $fields = $this->getAllFieldsAsArray('account');\n if (! in_array('Id', $fields, true)) {\n $this->logger->info('[Salesforce] Sync account cancelled. Fields are not available.', [\n 'crmId' => $crmId,\n 'userId' => $this->profile->getUserId(),\n ]);\n\n return null;\n }\n\n $sfAccount = $this->getRecord('Account', $crmId, $fields);\n\n return $this->importAccount($sfAccount);\n }\n\n private function importAccount($crmData): Account\n {\n $countryCode = $crmData['BillingCountryCode'] ?? $crmData['ShippingCountryCode'] ?? null;\n\n // Salesforce allows custom \"countries\" to be created. Disregard these.\n if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {\n $countryCode = null;\n }\n\n // If we have no country code, try to parse it from the country names.\n if ($countryCode === null && empty($crmData['BillingCountry']) === false) {\n $countryCode = $this->convertCountryNameToCode($crmData['BillingCountry']);\n }\n\n if ($countryCode === null && empty($crmData['ShippingCountry']) === false) {\n $countryCode = $this->convertCountryNameToCode($crmData['ShippingCountry']);\n }\n\n if (empty($crmData['Phone']) === false) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($crmData['Phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $industry = null;\n if (empty($crmData['Industry']) === false) {\n $industry = mb_strimwidth($crmData['Industry'], 0, 40);\n }\n\n $domain = null;\n if (empty($crmData['Website']) === false) {\n $domain = mb_strimwidth($crmData['Website'], 0, 191);\n $domain = StringUtil::resolveDomain($domain);\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $data = [\n 'team_id' => $this->team->id,\n 'user_id' => $profile?->user_id,\n 'owner_id' => $crmData['OwnerId'],\n 'name' => mb_strimwidth($crmData['Name'], 0, 191),\n 'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n crmConfiguration: $this->config,\n crmProviderId: $crmData['Id'],\n modelType: Account::class,\n fileName: $crmData['Id'],\n avatarText: $crmData['Name']\n ),\n 'industry' => $industry,\n 'domain' => $domain,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $createdDate,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->accounts(), $crmData['Id']);\n\n /** @var Account */\n $account = $this->config->accounts()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n $this->handleObjectDeletion($account, $crmData);\n\n return $account;\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n\n $syncCount = 0;\n $logParams = $parameters;\n $parameters['profile'] = $this->profile;\n $logParams['user'] = $this->profile->getUserId();\n\n if (count($strategies) > 1) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Multiple sync strategies used', [\n 'teamId' => $this->team->getUuid(),\n 'params' => $logParams,\n 'strategies_count' => count($strategies),\n ]);\n }\n\n foreach ($strategies as $syncStrategy) {\n $name = $syncStrategy->getStrategyName();\n\n try {\n $sfOpportunities = $syncStrategy->fetchOpportunities($parameters);\n $totalRecords = $sfOpportunities->count();\n\n foreach ($sfOpportunities as $sfOpportunity) {\n $this->importOpportunity($sfOpportunity);\n $syncCount++;\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n $this->logger->warning('[' . $this->getDisplayName() . '] No opportunities found', [\n 'teamId' => $this->team->getUuid(),\n 'name' => $name,\n 'params' => $logParams,\n 'reason' => $noResultsException->getMessage(),\n ]);\n } catch (CrmException $crmException) {\n // Nothing to sync.\n $this->logger->warning('[' . $this->getDisplayName() . '] Opportunity sync failed', [\n 'teamId' => $this->team->getUuid(),\n 'name' => $name,\n 'params' => $logParams,\n 'reason' => $crmException->getMessage(),\n ]);\n }\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::OPPORTUNITY, ['params' => $logParams]);\n\n // debug to see how if count of opportunities reaches 1000\n if ($syncCount >= 1000) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Sync Opportunities - count warning',\n [\n 'team_id' => $this->team->getId(),\n 'params' => $logParams,\n 'count' => $syncCount,\n 'strategies_count' => count($strategies),\n 'total_records' => $totalRecords ?? null,\n ]\n );\n }\n\n return $syncCount;\n }\n\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY\n );\n\n $parameters = [\n 'profile' => $this->profile,\n 'crm_id' => $crmId,\n ];\n\n try {\n $sfOpportunity = $strategy->fetchOpportunities($parameters);\n } catch (HttpNotFoundException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->id_string,\n 'crmId' => $crmId,\n ]);\n\n return null;\n } catch (CrmException $crmException) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity sync failed', [\n 'teamId' => $this->team->id_string,\n 'crmId' => $crmId,\n 'exception' => $crmException->getMessage(),\n ]);\n\n return null;\n }\n\n if ($sfOpportunity instanceof ArrayIterator) {\n return $this->importOpportunity($sfOpportunity->getItems());\n }\n\n return $this->importOpportunity($sfOpportunity);\n }\n\n private function importOpportunity($crmData): ?Opportunity\n {\n /** @var ?Stage $stage */\n $stage = null;\n if (isset($crmData['StageName'])) {\n $stage = $this->config\n ->stages()\n ->where('name', $crmData['StageName'])\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->orderBy('is_selectable', 'DESC')\n ->orderBy('id')\n ->first();\n\n if ($stage === null) {\n // Import it.\n $stage = $this->importStages([Stage::TYPE_OPPORTUNITY], $crmData['StageName']);\n }\n }\n\n $recordType = null;\n if (empty($crmData['RecordTypeId']) === false) {\n /** @var ?RecordType $recordType */\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $crmData['RecordTypeId'])\n ->first();\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $account = null;\n if (empty($crmData['AccountId']) === false) {\n /** @var ?Account $account */\n $account = $this->config->accounts()\n ->where('crm_provider_id', (string) $crmData['AccountId'])\n ->first();\n\n if ($account === null) {\n $account = $this->syncAccount($crmData['AccountId']);\n }\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $closeDate = null;\n if (empty($crmData['CloseDate']) === false) {\n $closeDate = Carbon::parse($crmData['CloseDate'])->format('Y-m-d');\n }\n\n $valueFieldName = 'Amount';\n if ($this->config->opportunity_value_field_id) {\n $valueFieldName = $this->config->opportunityValueField->crm_provider_id;\n }\n\n $data = [\n 'team_id' => $this->team->id,\n 'account_id' => $account->id ?? null,\n 'user_id' => $profile?->user_id ?? null,\n 'owner_id' => $crmData['OwnerId'] ?? null,\n 'name' => mb_strimwidth($crmData['Name'] ?? '', 0, 128),\n 'value' => $crmData[$valueFieldName],\n 'currency_code' => CurrencyFormatter::formatCode($crmData['CurrencyIsoCode'] ?? null),\n 'close_date' => $closeDate,\n 'is_closed' => $crmData['IsClosed'],\n 'is_won' => $crmData['IsWon'],\n 'stage_id' => $stage?->id ?? null,\n 'record_type_id' => $recordType->id ?? null,\n 'remotely_created_at' => $createdDate,\n 'probability' => $crmData['Probability'] ?? null,\n 'forecast_category' => $crmData['ForecastCategoryName'] ?? null,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->opportunities(), $crmData['Id']);\n\n // Do not allow locked DB tables & other errors\n // to interrupt the process of reverting the trashed opportunities\n try {\n /** @var Opportunity $opportunity */\n $opportunity = $this->config->opportunities()\n ->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n // import external fields into crm_field_data if present\n $crmFields = $this->getOpportunitySyncableFields();\n\n $this->importOpportunityCrmFieldData($crmData, $crmFields, $opportunity->id);\n\n $this->handleObjectDeletion($opportunity, $crmData);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n } catch (Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->error('[Salesforce] importOpportunity failure.', [\n 'crm_provider_id' => $crmData['Id'],\n 'team_id' => $this->team->id,\n 'exception' => $exception->getMessage(),\n ]);\n\n $this->handleEntityDeletionByProviderId($this->config->opportunities(), $crmData);\n }\n\n return null;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n $fields = $this->getAllFieldsAsArray('contact');\n if (\\in_array('Id', $fields, true) === false) {\n return $syncCount;\n }\n\n $query = '\n SELECT ' . rtrim(implode(',', $fields), ',') . '\n FROM Contact\n WHERE LastModifiedDate > :since\n ORDER BY LastModifiedDate ASC';\n\n try {\n $sfContacts = $this->queryHandler->query($query, [\n 'since' => $since->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($sfContacts as $sfContact) {\n // Only sync if previously imported.\n if ($this->hasContact($sfContact['Id'])) {\n $this->importContact($sfContact);\n $syncCount++;\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::CONTACT);\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n $fields = $this->getAllFieldsAsArray('contact');\n if (! in_array('Id', $fields, true)) {\n $this->logger->info('[Salesforce] Sync contact cancelled. Fields are not available.', [\n 'crmId' => $crmId,\n 'userId' => $this->profile->getUserId(),\n ]);\n\n return null;\n }\n\n $sfContact = $this->getRecord('Contact', $crmId, $fields);\n\n return $this->importContact($sfContact);\n }\n\n private function importContact($crmData): Contact\n {\n $account = null;\n // Contacts may not have accounts...\n if (isset($crmData['AccountId'])) {\n $account = $this->config->accounts()\n ->where('crm_provider_id', (string) $crmData['AccountId'])\n ->first();\n\n if ($account === null) {\n $account = $this->syncAccount($crmData['AccountId']);\n }\n }\n\n $countryCode = $crmData['MailingCountryCode'] ?? null;\n\n // Salesforce allows custom \"countries\" to be created. Disregard these.\n if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {\n $countryCode = null;\n }\n\n // If we have no country code, try to parse it from the country name.\n if ($countryCode === null && empty($crmData['MailingCountry']) === false) {\n $countryCode = $this->convertCountryNameToCode($crmData['MailingCountry']);\n\n if ($countryCode === null && $account) {\n $countryCode = $account->country_code;\n }\n }\n\n $ext = null;\n $parsedNumber = [];\n if (empty($crmData['Phone']) === false) {\n $number = Str::limit($crmData['Phone'], 25, '');\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n\n if (empty($parsedNumber['ext']) === false) {\n $ext = Str::limit($parsedNumber['ext'], 10, '');\n }\n }\n\n $mobileNumber = null;\n if (empty($crmData['MobilePhone']) === false) {\n $mobileNumber = Str::limit(phone_e164($countryCode, $crmData['MobilePhone']), 25, '');\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $data = [\n 'team_id' => $this->team->id,\n 'account_id' => $account->id ?? null,\n 'user_id' => $profile?->user_id,\n 'owner_id' => $crmData['OwnerId'] ?? null,\n 'name' => ($crmData['Name'] ?? null) !== null ? mb_strimwidth($crmData['Name'], 0, 100) : '',\n 'title' => ($crmData['Title'] ?? null) !== null ? mb_strimwidth($crmData['Title'], 0, 128) : null,\n 'email' => ($crmData['Email'] ?? null) !== null ? mb_strimwidth($crmData['Email'], 0, 191) : null,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $ext,\n 'mobile_phone' => $mobileNumber,\n 'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n crmConfiguration: $this->config,\n crmProviderId: $crmData['Id'],\n modelType: Contact::class,\n fileName: $crmData['Id'],\n avatarText: $crmData['Name']\n ),\n 'remotely_created_at' => $createdDate,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->contacts(), $crmData['Id']);\n\n /** @var Contact */\n $contact = $this->config->contacts()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n $this->handleObjectDeletion($contact, $crmData);\n\n return $contact;\n }\n\n /**\n * @inheritdoc\n */\n public function syncOrganization(): void\n {\n $fields = [\n 'InstanceName',\n 'OrganizationType',\n 'IsSandbox',\n ];\n\n $orgValues = $this->getRecord('Organization', $this->config->crm_provider_id, $fields);\n\n $edition = null;\n switch ($orgValues['OrganizationType']) {\n case 'Developer Edition':\n $edition = Configuration::EDITION_DEVELOPER;\n\n break;\n\n case 'Professional Edition':\n $edition = Configuration::EDITION_PROFESSIONAL;\n\n break;\n\n case 'Enterprise Edition':\n $edition = Configuration::EDITION_ENTERPRISE;\n\n break;\n }\n\n $this->config->edition = $edition;\n $this->config->instance = $orgValues['InstanceName'];\n\n // XXX: How can this state be possible?\n if ($this->config->version === null) {\n $this->config->version = Client::MIN_API_VERSION;\n }\n\n $installedVersion = $this->getInstalledAppVersion();\n if ($installedVersion !== null) {\n $installedVersion = (string) $this->getInstalledAppVersion();\n }\n\n $this->config->installed_app_version = $installedVersion;\n\n $this->config->save();\n }\n\n public function getInstalledAppVersion(): ?string\n {\n try {\n $query = '\n SELECT\n SubscriberPackageVersion.MajorVersion,\n SubscriberPackageVersion.MinorVersion,\n SubscriberPackageVersion.PatchVersion,\n SubscriberPackageVersion.BuildNumber\n FROM\n InstalledSubscriberPackage\n WHERE\n SubscriberPackageId = :packageId\n ';\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'packageId' => self::INSTALLED_PACKAGE_ID,\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n // Grab version number.\n $version = $sfField['SubscriberPackageVersion']['MajorVersion'] .\n $sfField['SubscriberPackageVersion']['MinorVersion'] .\n $sfField['SubscriberPackageVersion']['PatchVersion'] .\n $sfField['SubscriberPackageVersion']['BuildNumber'];\n } catch (\\Exception) {\n $version = null;\n }\n\n return $version;\n }\n\n public function saveActivity(Activity $activity): Activity\n {\n $playbook = $this->getPlaybookFromActivity($activity);\n\n if ($playbook === null) {\n throw new \\InvalidArgumentException('Please configure a Playbook first.');\n }\n\n $payload = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'Description' => (new DecorateActivity())->generateDescription($activity),\n ];\n\n // If the activity name matches a know activity type, set that here.\n if ($this->matchesCrmType($activity->category)) {\n $payload += [\n $playbook->activityField->crm_provider_id => $activity->category->name,\n 'Subject' => (new DecorateActivity())->generateTitle($activity),\n ];\n } else {\n $subject = $activity->category->name;\n if ($activity->getTitle() !== null) {\n $subject .= ': ' . $activity->getTitle();\n }\n\n $payload += [\n 'Subject' => $subject,\n ];\n }\n\n if ($activity->account_id) {\n $hasLinkFeature = $activity->user->team->hasFeature(\n FeatureEnum::LINK_ACTIVITY_TO_MULTIPLE_PROSPECTS\n );\n if (($hasLinkFeature && $activity->opportunity_id && ! $activity->contact_id)\n || (! $hasLinkFeature && $activity->hasOpportunity())\n ) {\n $payload += ['WhatId' => $activity->opportunity->crm_provider_id];\n } else {\n $payload += ['WhatId' => $activity->account->crm_provider_id];\n }\n }\n\n if ($activity->contact_id) {\n $payload += ['WhoId' => $activity->contact->crm_provider_id];\n } elseif ($activity->lead_id) {\n // Sync the lead to get fresh data instead of the cached one.\n $leadData = $this->syncLead($activity->lead->crm_provider_id);\n\n // Checking whenever we log if the lead is actually converted.\n if ($leadData['converted_at'] !== null) {\n $convertedLead = $this->config->leads()->find($activity->lead_id);\n $convertedOpportunity = null;\n $activity->lead_id = null;\n\n if ($convertedLead) {\n if ($convertedLead->account) {\n // Overwrite account's crm_provider_id with converted account one.\n $payload += ['WhatId' => $convertedLead->account->crm_provider_id];\n\n $activity->account_id = $convertedLead->account->id;\n }\n\n if ($convertedLead->opportunity) {\n // Overwrite opportunity crm_provider_id with converted opportunity one.\n $convertedOpportunity = $convertedLead->opportunity;\n $payload += ['WhatId' => $convertedOpportunity->crm_provider_id];\n\n $activity->opportunity_id = $convertedOpportunity->id;\n $activity->value = $convertedOpportunity->value;\n }\n\n if ($convertedLead->contact) {\n // Overwrite contact crm_provider_id with converted contact one.\n $payload += ['WhoId' => $convertedLead->contact->crm_provider_id];\n\n $activity->contact_id = $convertedLead->contact->id;\n }\n }\n\n // If there is converted opportunity, use it to update the stage, otherwise update it to null,\n // because you can’t have an account/contact on an activity and a lead stage.\n $activity->stage_id = $convertedOpportunity ? $convertedOpportunity['stage_id'] : null;\n\n // Update activity with correct data, pointing to the correct account/contact/opportunity and stage.\n $activity->save();\n\n event(new ActivityLeadConverted($activity, $leadData));\n } else {\n $payload += [\n 'WhoId' => $activity->lead->crm_provider_id,\n 'WhatId' => null, // In case it was set on the remote record.\n ];\n }\n }\n\n if ($playbook->activity_type === Playbook::ACTIVITY_TYPE_TASK) {\n // Generate payload.\n $payload = array_merge($payload, $this->buildTaskPayload($activity));\n\n // Check if the activity should be logged under an existing task or created fresh.\n if ($activity->hasCrmProviderId()) {\n $this->updateCrmActivity(Field::OBJECT_TASK, $activity, $payload);\n } else {\n $activityId = $this->createRecord('Task', $payload);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n }\n } else {\n // Generate payload.\n $payload = array_merge($payload, $this->buildEventPayload($activity));\n\n // Check if the activity should be logged under an existing event or created fresh.\n if ($activity->hasCrmProviderId()) {\n $this->updateCrmActivity(Field::OBJECT_EVENT, $activity, $payload);\n } else {\n $activityId = $this->createRecord('Event', $payload);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n }\n }\n\n return $activity;\n }\n\n private function updateCrmActivity(string $objectType, Activity $activity, array $payload): void\n {\n $sfActivity = $this->getRecord(\n objectType: $objectType,\n objectId: $activity->getCrmProviderId(),\n fields: ['Description', 'WhoId', 'WhatId']\n );\n\n if (! empty($payload['WhoId']) && $sfActivity['WhoId'] !== $payload['WhoId']) {\n $this->logger->info('[Salesforce] Updating WhoId', [\n 'objectType' => $objectType,\n 'activityId' => $activity->getUuid(),\n ]);\n\n if (! empty($payload['WhatId']) && $sfActivity['WhatId'] !== $payload['WhatId']) {\n $this->logger->info('[Salesforce] Updating WhatId', [\n 'objectType' => $objectType,\n 'activityId' => $activity->getUuid(),\n ]);\n } else {\n $payload['WhatId'] = null;\n }\n }\n\n $decorateActivity = new DecorateActivity();\n $payload['Description'] = $decorateActivity->mergeDescriptions(\n $payload['Description'],\n $sfActivity['Description'] ?? null\n );\n\n $this->logger->info('[Salesforce] Updating CRM activity data', [\n 'objectType' => $objectType,\n 'activityId' => $activity->getUuid(),\n 'crmActivity' => $sfActivity,\n 'payload' => $payload,\n ]);\n\n $this->updateRecord(\n objectType: $objectType,\n objectId: $activity->getCrmProviderId(),\n data: $payload\n );\n }\n\n /**\n * Store transcripts as note.\n *\n * @throws \\Exception\n */\n public function createTranscriptNotes(Activity $activity): void\n {\n // For SF we also check if Log Notes is enabled.\n if ($this->profile->log_notes === Profile::LOG_NOTE_NONE) {\n return;\n }\n\n if ($activity->opportunity_id && $activity->prospect === null) {\n return;\n }\n\n try {\n $transcriptionData = $this->generateTranscription($activity);\n\n $noteMaxLength = $this->profile->log_notes === Profile::LOG_NOTE_ENHANCED\n ? self::ENHANCED_NOTE_MAX_LENGTH\n : self::CLASSIC_NOTE_MAX_LENGTH;\n\n $title = 'Transcript for ';\n $title .= $activity->title ?? $activity->activity_title;\n\n // Truncate Notes with max notes length because transcription text could be very long.\n $body = mb_strimwidth($transcriptionData, 0, $noteMaxLength);\n\n if ($activity->opportunity_id) {\n $objectId = $activity->opportunity->crm_provider_id;\n } else {\n $objectId = $activity->prospect->crm_provider_id;\n }\n\n $noteId = $this->saveNote($title, $body, $objectId);\n\n // Store crm logged id in transcription.\n $transcription = $activity->getTranscription();\n $transcription->crm_activity_id = $noteId;\n $transcription->save();\n } catch (\\Exception $e) {\n \\Sentry::captureException($e);\n }\n }\n\n private function buildTaskPayload(Activity $activity): array\n {\n $payload = [\n 'Status' => 'Completed',\n ];\n\n switch ($activity->getCrmType()) {\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n case Activity::TYPE_CONFERENCE:\n\n // \"Due Date\" is stored as UTC and should reflect the users local time preference.\n $activityDate = $activity->actual_start_time\n ? $activity->actual_start_time->tz($activity->user->timezone)->toDateString()\n : $activity->created_at->tz($activity->user->timezone)->toDateString();\n\n if ($activity->is_internal) {\n $callType = 'Internal';\n } elseif ($activity->isTypeSoftPhone() && $activity->getProvider() !== Activity::PROVIDER_UPLOADER) {\n $callType = 'Outbound';\n } else {\n $callType = 'Inbound';\n }\n\n $payload += [\n 'CallDurationInSeconds' => $activity->duration,\n 'CallType' => $callType,\n 'CallObject' => $activity->getUuid(),\n 'ActivityDate' => $activityDate,\n ];\n\n if ($activity->crm_provider_id === null) {\n // Fields that can only be set on initial creation.\n $payload += [\n 'TaskSubtype' => 'Call',\n ];\n }\n\n break;\n\n case Activity::TYPE_SMS_OUTBOUND:\n case Activity::TYPE_SMS_INBOUND:\n default:\n $payload += [\n 'ActivityDate' => $activity->created_at->tz($activity->user->timezone)->toDateString(),\n ];\n\n break;\n }\n\n $payload = $this->payloadBuilder\n ->addCustomLogicFieldsPayload($activity, $payload, Field::OBJECT_TASK);\n\n return array_merge($payload, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n }\n\n private function buildEventPayload(Activity $activity): array\n {\n $startDateTime = $activity->scheduled_start_time;\n if ($activity->actual_start_time) {\n $startDateTime = $activity->actual_start_time;\n }\n\n $endDateTime = $activity->scheduled_end_time;\n if ($activity->actual_end_time) {\n $endDateTime = $activity->actual_end_time;\n }\n\n // If the call never closed (e.g. cancelled) just set EndDateTime to StartDateTime.\n if ($endDateTime === null) {\n $endDateTime = $startDateTime;\n }\n\n $payload = [\n 'StartDateTime' => $startDateTime->format('Y-m-d\\TH:i:s\\Z'),\n 'EndDateTime' => $endDateTime->format('Y-m-d\\TH:i:s\\Z'),\n ];\n\n $payload = $this->payloadBuilder->addCustomLogicFieldsPayload($activity, $payload, Field::OBJECT_EVENT);\n\n return array_merge($payload, $this->fetchCustomFieldData($activity, Field::OBJECT_EVENT));\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Check the field is custom and add it to the payload.\n if ($this->isCustomField($data->getField())) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n }\n\n return $payload;\n }\n\n public function saveFollowupActivity(Activity $fromActivity, array $fields): ?string\n {\n $playbook = $this->getPlaybook($fromActivity->getUser());\n\n if ($playbook === null) {\n throw new \\InvalidArgumentException('Please configure a Playbook first.');\n }\n\n $activityType = $playbook->getActivityType();\n // This is the user provided activity type field.\n // Todo: don't require subject and instead check if a date/startdate is set.\n if (empty($fields['Subject']) && empty($fields['Type'])) {\n return null;\n }\n\n $activityTypeField = $playbook->activityField->crm_provider_id;\n\n $payload = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'Subject' => $fields['Subject'] ?? $fields['Type'] . ' with ' . $fromActivity->prospect_name,\n $activityTypeField => $fields['Type'] ?? null,\n ];\n\n if ($fromActivity->account) {\n if ($fromActivity->opportunity) {\n $payload += ['WhatId' => $fromActivity->opportunity->crm_provider_id];\n } else {\n $payload += ['WhatId' => $fromActivity->account->crm_provider_id];\n }\n }\n\n if ($fromActivity->contact) {\n $payload += ['WhoId' => $fromActivity->contact->crm_provider_id];\n } elseif ($fromActivity->lead) {\n $payload += ['WhoId' => $fromActivity->lead->crm_provider_id];\n }\n\n if ($activityType === Playbook::ACTIVITY_TYPE_TASK) {\n // Generate payload.\n $payload = array_merge($payload, $this->buildFollowupTaskPayload($fields));\n\n $activityId = $this->createRecord('Task', $payload);\n } else {\n // Generate payload.\n $payload = array_merge($payload, $this->buildFollowupEventPayload($fields));\n\n $activityId = $this->createRecord('Event', $payload);\n }\n\n // We don't actually create a corresponding activity object on our side yet.\n return $activityId;\n }\n\n private function buildFollowupTaskPayload(array $fields): array\n {\n $payload = [\n 'ActivityDate' => $fields['ActivityDate'] ?? date('Y-m-d'),\n 'TaskSubtype' => 'Call',\n ];\n\n if (empty($fields['Priority']) === false) {\n $payload += [\n 'Priority' => $fields['Priority'],\n ];\n }\n\n if (empty($fields['Description']) === false) {\n $payload += [\n 'Description' => $fields['Description'],\n ];\n }\n\n if (empty($fields['Status']) === false) {\n $payload += [\n 'Status' => $fields['Status'],\n ];\n }\n\n if (empty($fields['ReminderDateTime']) === false) {\n $payload += [\n 'ReminderDateTime' => $fields['ReminderDateTime'],\n 'IsReminderSet' => true,\n ];\n }\n\n return $payload;\n }\n\n private function buildFollowupEventPayload(array $fields): array\n {\n $payload = [\n 'StartDateTime' => $fields['StartDateTime'] ?? date('Y-m-d\\TH:i:s\\Z'),\n ];\n\n if (empty($fields['EndDateTime'])) {\n $payload += [\n 'IsAllDayEvent' => true,\n ];\n } else {\n $payload += [\n 'EndDateTime' => $fields['EndDateTime'],\n ];\n }\n\n if (empty($fields['Description']) === false) {\n $payload += [\n 'Description' => $fields['Description'],\n ];\n }\n\n if (empty($fields['Status']) === false) {\n $payload += [\n 'Status' => $fields['Status'],\n ];\n }\n\n if (empty($fields['ReminderDateTime']) === false) {\n $payload += [\n 'ReminderDateTime' => $fields['ReminderDateTime'],\n 'IsReminderSet' => true,\n ];\n }\n\n return $payload;\n }\n\n public function saveNote(string $title, string $body, string $objectId, ?NoteObject $noteObject = null): ?string\n {\n $noteId = null;\n\n try {\n if ($this->profile->log_notes === Profile::LOG_NOTE_ENHANCED) {\n $noteId = $this->buildEnhancedNote($title, $body, $objectId);\n } else {\n $noteId = $this->buildClassicNote($title, $body, $objectId);\n }\n } catch (HttpNotFoundException $exception) {\n // The profile not having access to create Enhanced Notes. Set their preference to Classic.\n if ($this->profile->log_notes === Profile::LOG_NOTE_ENHANCED) {\n $this->profile->update([\n 'log_notes' => Profile::LOG_NOTE_CLASSIC,\n ]);\n }\n }\n\n return $noteId;\n }\n\n /**\n * This is using the \"Enhanced\" Notes feature, NOT the \"Notes & Attachments\" feature being deprecated.\n *\n * @url https://salesforce.stackexchange.com/questions/104408/how-can-i-create-an-account-note-or-contact-note-via-api-that-is-visible-in-sale\n */\n private function buildEnhancedNote(string $title, string $body, string $objectId): string\n {\n // Decode stored entities, escape HTML (without quoting), then convert line breaks for Salesforce formatting\n $decodedBody = html_entity_decode($body, ENT_QUOTES | ENT_HTML5);\n $sanitizedBody = htmlspecialchars($decodedBody, ENT_NOQUOTES, 'UTF-8', false);\n $content = nl2br($sanitizedBody, false);\n $note = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'Title' => $title,\n 'Content' => base64_encode($content),\n ];\n\n $noteId = $this->createRecord('ContentNote', $note);\n\n $link = [\n 'ContentDocumentId' => $noteId,\n 'LinkedEntityId' => $objectId,\n 'ShareType' => 'I',\n ];\n\n $this->createRecord('ContentDocumentLink', $link);\n\n return $noteId;\n }\n\n private function buildClassicNote(string $title, string $body, string $objectId): string\n {\n if (in_array($this->parseObjectType($objectId), [Field::OBJECT_TASK, Field::OBJECT_EVENT])) {\n $this->logger->info('[Salesforce] Summary not sent', [\n 'profile_id' => $this->profile->id,\n 'objectId' => $objectId,\n 'reason' => 'Classical Note does not support Task/Event relation',\n ]);\n\n return '';\n }\n\n $titleTrimmed = null;\n\n if (mb_strlen($title) > 80) {\n $titleTrimmed = substr($title, 0, 77) . '...';\n }\n $payload = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'IsPrivate' => false,\n 'Title' => $titleTrimmed ?? $title,\n 'Body' => $titleTrimmed ? $title . PHP_EOL . $body : $body,\n 'ParentId' => $objectId,\n ];\n\n return $this->createRecord('Note', $payload);\n }\n\n /**\n * @inheritdoc\n */\n public function find(string $name, array $scopes): array\n {\n if ($this->profile === null) {\n return [];\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $limitValues = ['limit' => $this->limit, 'offset' => $this->offset];\n $sosl = $queryBuilder->buildFindQuery($name, $scopes, $limitValues);\n\n $this->logger->info('[Salesforce] Find prospects', [\n 'profile_id' => $this->profile->id,\n 'sosl_query' => $sosl,\n 'search_string' => $name,\n 'scopes' => $scopes,\n ]);\n\n $data = Cache::remember($this->profile->id . $sosl, self::CACHE_TTL, function () use ($sosl) {\n $data = [];\n\n try {\n // Hit remote API.\n $objects = $this->queryHandler->search($sosl);\n\n // Build mapped list.\n foreach ($objects as $object) {\n $type = strtolower($object['attributes']['type']);\n\n $record = [\n 'crmId' => $object['Id'],\n 'name' => $object['Name'],\n 'prospectType' => $type,\n 'phoneNumbers' => [],\n 'crmUrl' => $this->generateProviderUrl($object['Id'], $type),\n ];\n\n switch ($type) {\n case 'lead':\n if (empty($object['Company']) === false) {\n $record['organization'] = $object['Company'];\n }\n\n if (empty($object['Title']) === false) {\n $record['title'] = $object['Title'];\n }\n\n $stage = $this->config->stages()\n ->where('type', Stage::TYPE_LEAD)\n ->where('name', $object['Status'])\n ->first();\n\n // Lazy create the stage.\n if ($stage === null) {\n $stage = $this->importStages([Stage::TYPE_LEAD], $object['Status']);\n }\n\n if ($stage) {\n $record += [\n 'stage' => [\n 'id' => $stage->id_string,\n 'name' => $stage->name,\n ],\n ];\n }\n\n if (empty($object['RecordTypeId']) === false) {\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $object['RecordTypeId'])\n ->first();\n\n if ($recordType) {\n $record += [\n 'recordType' => [\n 'id' => $recordType->id_string,\n 'name' => $recordType->name,\n ],\n ];\n }\n }\n\n break;\n\n case 'account':\n if (empty($object['Industry']) === false) {\n $record['industry'] = $object['Industry'];\n $record['detailsLine'] = $object['Industry'];\n }\n if (! empty($object['PersonEmail'])) {\n $record['detailsLine'] = $object['PersonEmail'];\n }\n\n break;\n\n case 'contact':\n // For contacts, we should try and fetch their account name too.\n if ($object['AccountId']) {\n // Cheaper to get this locally.\n $account = $this->config->accounts()\n ->where('crm_provider_id', $object['AccountId'])\n ->first(['name']);\n\n if ($account) {\n $record['organization'] = $account->name;\n }\n }\n\n if (! empty($object['IsPersonAccount']) && $object['Email']) {\n $record['detailsLine'] = $object['Email'];\n } else {\n if (empty($object['Title']) === false) {\n $record['title'] = $object['Title'];\n }\n }\n\n break;\n }\n\n // Add phone numbers to record.\n if (empty($object['Phone']) === false && $object['Phone']) {\n $record['phoneNumbers'][] = [\n 'number' => $object['Phone'],\n 'nationalFormat' => phone_national($this->profile->user->country_code, $object['Phone']),\n 'type' => 'phone',\n ];\n }\n\n if (empty($object['MobilePhone']) === false && $object['MobilePhone']) {\n $record['phoneNumbers'][] = [\n 'number' => $object['MobilePhone'],\n 'nationalFormat' => phone_national(\n $this->profile->user->country_code,\n $object['MobilePhone']\n ),\n 'type' => 'mobile',\n ];\n }\n\n $data[] = $record;\n }\n } catch (NoResultsException $e) {\n $data = [];\n }\n\n return $data;\n });\n\n return $data;\n }\n\n /**\n * @inheritdoc\n */\n public function findOpportunities(?string $crmAccountId, ?string $crmContactId, ?int $userId = null): array\n {\n $data = [];\n $ownerData = [];\n $ownerId = null;\n\n if ($crmAccountId === null) {\n return $data;\n }\n\n if ($userId) {\n $profileRepository = app(ProfileRepository::class);\n $profile = $profileRepository->findProfileByUserId($this->config, $userId);\n\n $ownerId = $profile instanceof Profile ? $profile->getCrmProviderId() : null;\n }\n\n try {\n // Perhaps their profile has no opportunity permissions.\n if ($this->profile === null || $this->profile->opportunity_fields === null) {\n return $data;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $query = $queryBuilder->buildFindOpportunitiesQuery();\n\n $objects = $this->queryHandler->query($query, ['accountId' => $crmAccountId]);\n\n foreach ($objects as $object) {\n $record = [\n 'crmId' => $object['Id'],\n 'name' => $object['Name'],\n 'won' => $object['IsWon'],\n 'closed' => $object['IsClosed'],\n ];\n\n $valueFieldName = 'Amount';\n if ($this->config->opportunity_value_field_id) {\n $valueFieldName = $this->config->opportunityValueField->crm_provider_id;\n }\n\n if (empty($object[$valueFieldName]) === false) {\n $currency = $object['CurrencyIsoCode'] ?? $this->config->default_currency;\n $value = formatCurrency($object[$valueFieldName], $currency);\n\n $record += [\n 'value' => $value,\n ];\n }\n\n $stage = $this->config->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->where('name', $object['StageName'])\n ->first();\n\n // Lazy create the stage.\n if ($stage === null) {\n $stage = $this->importStages([Stage::TYPE_OPPORTUNITY], $object['StageName']);\n }\n\n $record += [\n 'stage' => [\n 'id' => $stage->id_string,\n 'name' => $stage->name,\n ],\n ];\n\n if (empty($object['RecordTypeId']) === false) {\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $object['RecordTypeId'])\n ->first();\n\n if ($recordType) {\n $record += [\n 'recordType' => [\n 'id' => $recordType->id_string,\n 'name' => $recordType->name,\n ],\n ];\n }\n }\n\n if ($ownerId && isset($object['OwnerId']) && $object['OwnerId'] === $ownerId) {\n $ownerData[] = $record;\n }\n\n $data[] = $record;\n }\n } catch (NoResultsException $e) {\n return $data;\n }\n\n if (! empty($ownerData)) {\n return $ownerData;\n }\n\n return $data;\n }\n\n public function getContactRolesFromCrm(?Carbon $since = null): array\n {\n $roles = [];\n\n if ($this->profile === null) {\n return $roles;\n }\n\n $queryBuilder = app(QueryBuilder::class, ['profile' => $this->profile]);\n\n $query = $queryBuilder->buildGetContactRolesQuery($since);\n\n try {\n $objects = $this->queryHandler->query($query);\n\n foreach ($objects as $object) {\n $roles[] = [\n 'id' => $object['Id'],\n 'contactId' => $object['ContactId'],\n 'opportunityId' => $object['OpportunityId'],\n 'ownerId' => $object['Opportunity']['OwnerId'] ?? null,\n 'isPrimary' => $object['IsPrimary'],\n 'role' => $object['Role'],\n ];\n }\n } catch (NoResultsException) {\n // Just return an empty array.\n $this->logger->info('[Salesforce] No contact roles found', [\n 'since' => $since?->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n }\n\n return $roles;\n }\n\n public function syncContactRoles(Carbon $since): int\n {\n $contactRoleRepository = app(ContactRoleRepository::class);\n\n $crmContactRoles = $this->getContactRolesFromCrm(since: $since);\n $syncCount = 0;\n $contactRoles = [];\n\n foreach ($crmContactRoles as $crmContactRole) {\n $contactRoles[] = $this->importContactRole($crmContactRole);\n $syncCount++;\n }\n\n $contactRoleRepository->saveContactRoles($contactRoles);\n\n $this->syncRemotelyDeletedContactRoles();\n\n return $syncCount;\n }\n\n private function importContactRole(array $contactRole): array\n {\n $contact = $this->config->contacts()\n ->where('crm_provider_id', $contactRole['contactId'])\n ->first();\n\n if ($contact === null) {\n $contact = $this->syncContact($contactRole['contactId']);\n }\n\n $opportunity = $this->config->opportunities()\n ->where('crm_provider_id', $contactRole['opportunityId'])\n ->first();\n\n if ($opportunity === null) {\n $opportunity = $this->syncOpportunity($contactRole['opportunityId']);\n }\n\n $role = null;\n if (! empty($contactRole['role'])) {\n $role = mb_strimwidth($contactRole['role'], 0, 191);\n }\n\n return [\n 'crm_configuration_id' => $this->config->getId(),\n 'contact_id' => $contact->getId(),\n 'crm_provider_id' => $contactRole['id'],\n 'subject_type' => ContactRole::SUBJECT_TYPE_OPPORTUNITY,\n 'subject_id' => $opportunity->getId(),\n 'is_primary' => $contactRole['isPrimary'],\n 'role' => $role,\n ];\n }\n\n protected function syncRemotelyDeletedContactRoles(): bool\n {\n try {\n $deletedRemotely = $this->queryHandler->queryDeleted('OpportunityContactRole');\n } catch (NoResultsException $e) {\n return false;\n }\n\n $deletedOpportunities = $deletedRemotely->getResults();\n $deletedIds = array_column($deletedOpportunities, 'id');\n\n $contactRoleRepository = app(ContactRoleRepository::class);\n\n foreach (array_chunk($deletedIds, self::HARD_DELETE_CHUNK) as $chunk) {\n $contactRoleRepository->deleteContactRoles($chunk);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Remotely deleted opportunities synced', [\n 'teamId' => $this->team->id_string,\n 'remotelyDeletedOpportunities' => $chunk,\n 'count' => count($chunk),\n ]);\n }\n\n return true;\n }\n\n /**\n * @inheritdoc\n */\n public function getTasks(string $objectType, string $objectId, ?string $opportunityId): array\n {\n $data = $objects = [];\n\n $hasWho = \\in_array($objectType, ['lead', 'contact']);\n $playbook = $this->getPlaybook($this->profile->user);\n $fields = array_merge(\n $this->profile->getFieldsAsArray(parent::OBJECT_TASK),\n $playbook && $playbook->activityField ? [$playbook->activityField->crm_provider_id] : []\n );\n\n // Query should default to any open call for that user.\n $query = '\n SELECT ' . implode(',', array_unique($fields)) . '\n FROM Task\n WHERE OwnerId = :ownerId\n AND IsArchived = false\n AND IsDeleted = false\n AND IsClosed = false\n AND (';\n\n if ($objectType === 'account') {\n // This covers tasks tied to a related contact or opportunity too.\n $query .= '\n AccountId = :accountId';\n }\n\n if ($hasWho) {\n $query .= '\n WhoId = :whoId';\n\n // If we are also going to check on a specific opportunity, set that up.\n if ($opportunityId) {\n $query .= ' OR WhatId = :whatId';\n }\n }\n\n $query .= ' ) ORDER BY LastModifiedDate DESC';\n\n try {\n $objects = $this->queryHandler->query($query, [\n 'ownerId' => $this->profile->crm_provider_id,\n 'whoId' => $objectId,\n 'whatId' => $opportunityId,\n 'accountId' => $objectId,\n ]);\n } catch (NoResultsException $e) {\n return $data;\n } finally {\n $this->logger->debug(sprintf('[Salesforce] Found %s tasks for query \"%s\"', count($objects), $query));\n }\n\n foreach ($objects as $object) {\n $dueDate = $object['ActivityDate'] ? Carbon::parse($object['ActivityDate'])->toIso8601String() : null;\n $data[] = [\n 'crmId' => $object['Id'],\n 'subject' => $object['Subject'],\n 'due' => $dueDate,\n 'type' => $object[$playbook->activityField->crm_provider_id],\n ];\n }\n\n return $data;\n }\n\n /**\n * @inheritdoc\n */\n public function getEvents(string $objectType, string $objectId, ?string $opportunityId): array\n {\n $data = $objects = [];\n $user = $this->profile?->user;\n if ($this->profile === null || $user === null) {\n return $data;\n }\n\n $hasWho = \\in_array($objectType, ['lead', 'contact']);\n $playbook = $this->getPlaybook($user);\n $fields = array_merge(\n $this->profile->getFieldsAsArray(parent::OBJECT_EVENT),\n $playbook && $playbook->activityField ? [$playbook->activityField->crm_provider_id] : []\n );\n\n // Query should default to any event starting in the last week and ending up until today owned by the user.\n $query = '\n SELECT ' . implode(',', array_unique($fields)) . '\n FROM Event\n WHERE OwnerId = :ownerId\n AND IsArchived = false\n AND IsAllDayEvent = false\n AND StartDateTime >= LAST_N_DAYS:7\n AND EndDateTime <= TODAY\n AND (';\n\n if ($objectType === 'account') {\n // This covers events tied to a related contact or opportunity too.\n $query .= '\n AccountId = :accountId';\n }\n\n if ($hasWho) {\n $query .= '\n WhoId = :whoId';\n\n // If we are also going to check on a specific opportunity, set that up.\n if ($opportunityId) {\n $query .= ' OR WhatId = :whatId';\n }\n }\n\n $query .= ' ) ORDER BY LastModifiedDate DESC';\n\n try {\n $objects = $this->queryHandler->query($query, [\n 'ownerId' => $this->profile->crm_provider_id,\n 'whoId' => $objectId,\n 'whatId' => $opportunityId,\n 'accountId' => $objectId,\n ]);\n } catch (NoResultsException $e) {\n return $data;\n } finally {\n $this->logger->debug(sprintf('[Salesforce] Found %s tasks for query \"%s\"', count($objects), $query));\n }\n\n foreach ($objects as $object) {\n $dueDate = $object['StartDateTime'] ? Carbon::parse($object['StartDateTime'])->toIso8601String() : null;\n\n $data[] = [\n 'crmId' => $object['Id'],\n 'subject' => $object['Subject'],\n 'due' => $dueDate,\n 'type' => $object[$playbook->activityField->crm_provider_id],\n ];\n }\n\n return $data;\n }\n\n /**\n * Try to find CRM Objects using email address\n *\n * @return null|array{\n * Lead|null,\n * Account|null,\n * Opportunity|null,\n * Contact|null,\n * Stage|null,\n * string|null\n * }\n */\n public function matchExactlyByEmail(string $email, ?int $userId = null): ?array\n {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $sosl = $queryBuilder->buildMatchByQuery($email, Field::TYPE_EMAIL);\n if ($sosl === null) {\n return null;\n }\n\n try {\n $objects = $this->queryHandler->search($sosl);\n $objects = $this->queryHandler->prioritiseResults(\n $objects,\n $email,\n QueryHandler::PRIORITISE_EMAIL\n );\n\n $data = $this->convertCrmData($objects, $userId);\n\n return ! empty(array_filter($data)) ? $data : null;\n } catch (NoResultsException $e) {\n // Try the account next.\n if ($this->profile->account_fields === null) {\n return null;\n }\n }\n\n return null;\n }\n\n public function getDomain(string $email): ?string\n {\n // SF improved search - strip the domain extension, min domain name length 4\n return $this->getCompanyNameFromEmail(email: $email, minNameLength: 4);\n }\n\n /**\n * Try to find CRM objects using domain name of the email address\n *\n * @return null|array{\n * Lead|null,\n * Account|null,\n * Opportunity|null,\n * Contact|null,\n * Stage|null,\n * string|null\n * }\n */\n public function matchByDomain(string $domain, ?int $userId = null): ?array\n {\n $companyName = $domain;\n\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $sosl = $queryBuilder->buildMatchByDomainQuery($companyName);\n\n try {\n $objects = $this->queryHandler->search($sosl);\n\n $data = $this->convertCrmData($objects, $userId);\n\n return ! empty(array_filter($data)) ? $data : null;\n } catch (NoResultsException) {\n return null;\n }\n }\n\n public function matchByPhone(string $phone, ?string $rawPhoneNumber = null, ?int $userId = null): ?array\n {\n // Don't bother looking up numbers that are masked.\n if (str_contains($phone, '**')) {\n return null;\n }\n\n if ($this->isPhoneNumberOfTeamMember($phone)) {\n return null;\n }\n\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $phoneNational = phone_national(null, $phone) ?? '';\n $possiblePhoneFormats = collect([\n preg_replace('/\\D/', '', ltrim($phone, '0+')),\n preg_replace('/\\D/', '', $phoneNational),\n formatDashPhoneNumber($phone),\n $phoneNational,\n ])\n ->filter() // Removes null and empty strings\n ->unique()\n ->values();\n\n foreach ($possiblePhoneFormats as $phone) {\n $sosl = $queryBuilder->buildMatchByQuery($phone, Field::TYPE_PHONE);\n if ($sosl === null) {\n continue;\n }\n\n try {\n $objects = $this->queryHandler->search($sosl);\n $objects = $this->queryHandler->prioritiseResults(\n $objects,\n $phone,\n QueryHandler::PRIORITISE_PHONE\n );\n\n return $this->convertCrmData($objects, $userId);\n } catch (NoResultsException) {\n continue;\n }\n }\n\n return null;\n }\n\n private function isPhoneNumberOfTeamMember(string $phone): bool\n {\n $teamRepository = app(TeamRepository::class);\n $user = $teamRepository->findTeamMemberByPhone($this->team, $phone);\n\n if ($user instanceof User) {\n return true;\n }\n\n return false;\n }\n\n protected function getCacheKey(string $object, ?int $userId = null): ?string\n {\n $key = $this->profile->id . $object;\n $keySuffix = $this->getOwnerKeySuffix($userId);\n\n return $key . $keySuffix;\n }\n\n private function getOwnerKeySuffix(?int $userId = null): string\n {\n return $userId === null ? '' : (string) $userId;\n }\n\n /** Determine the CRM Objects which represent the call activity. */\n public function matchByName(string $name, ?int $userId = null): ?array\n {\n // Don't waste time searching for single character strings.\n if (\\strlen($name) <= 1) {\n return null;\n }\n\n if ($this->profile === null) {\n return null;\n }\n\n $cacheKey = $this->getCacheKey($name, $userId);\n\n $result = Cache::remember($cacheKey, 60, function () use ($name, $userId) {\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $sosl = $queryBuilder->buildMatchByQuery($name, 'name');\n if ($sosl === null) {\n return false;\n }\n\n try {\n $objects = $this->queryHandler->search($sosl);\n } catch (NoResultsException $e) {\n return false;\n }\n\n $objects = $this->queryHandler->prioritiseResults(\n $objects,\n $name,\n QueryHandler::PRIORITISE_NAME\n );\n\n $data = $this->convertCrmData($objects, $userId);\n\n return (! empty(array_filter($data))) ? $data : false;\n });\n\n return is_array($result) ? $result : null;\n }\n\n /**\n * @return array{\n * Lead|null,\n * Account|null,\n * Opportunity|null,\n * Contact|null,\n * Stage|null,\n * string|null\n * }\n */\n protected function convertCrmData(QueryIterator $objects, ?int $userId = null): array\n {\n $lead = null;\n $contact = null;\n $opportunity = null;\n $account = null;\n $stage = null;\n $countryCode = null;\n\n if ($objects->count() > 0) {\n $object = $objects->current();\n\n if ($object['attributes']['type'] === 'Lead') {\n $lead = $this->importLead($object);\n\n // Lead might not be imported if the Stage is null for example.\n if ($lead) {\n $countryCode = $lead->country_code;\n $stage = $lead->stage;\n }\n } else {\n if ($object['attributes']['type'] === 'Contact') {\n $contact = $this->importContact($object);\n $account = $contact->account;\n } else {\n $account = $this->importAccount($object);\n }\n\n if ($contact && $contact->country_code) {\n $countryCode = $contact->country_code;\n } elseif ($account) {\n $countryCode = $account->country_code;\n }\n\n try {\n $sfOpportunities = $this->findOpportunities(\n $account?->getCrmProviderId(),\n $contact?->getCrmProviderId(),\n $userId\n );\n\n // Take the first opportunity, which will be ordered as priority based on their settings.\n if (! empty($sfOpportunities)) {\n // Persist this remote object.\n $opportunity = $this->syncOpportunity($sfOpportunities[0]['crmId']);\n $stage = $opportunity?->stage;\n }\n } catch (Exception) {\n // Nothing to see here.\n }\n }\n }\n\n return [\n $lead,\n $account,\n $opportunity,\n $contact,\n $stage,\n $countryCode,\n ];\n }\n\n /**\n * @inheritdoc\n */\n public function updateStage($crmObject, Stage $stage): void\n {\n if ($stage->type === Stage::TYPE_LEAD) {\n $objectType = 'Lead';\n $objectId = $crmObject->crm_provider_id;\n $objectStageType = 'Status';\n } else {\n $objectType = 'Opportunity';\n $objectId = $crmObject->crm_provider_id;\n $objectStageType = 'StageName';\n }\n\n $headers = [];\n if ($this->config->trigger_assignment_rules === false) {\n // @see: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/headers_autoassign.htm\n $headers = [\n 'Sforce-Auto-Assign' => 'false',\n ];\n }\n\n $this->updateRecord($objectType, $objectId, [$objectStageType => $stage->name], $headers);\n }\n\n public function parseObjectType(string $objectId): string\n {\n if (Str::startsWith($objectId, '001')) {\n return 'account';\n }\n\n if (Str::startsWith($objectId, '003')) {\n return 'contact';\n }\n\n if (Str::startsWith($objectId, '00Q')) {\n return 'lead';\n }\n\n if (Str::startsWith($objectId, '006')) {\n return 'opportunity';\n }\n\n if (Str::startsWith($objectId, '00U')) {\n return 'event';\n }\n\n if (Str::startsWith($objectId, '00T')) {\n return 'task';\n }\n\n throw new \\InvalidArgumentException('Unsupported Object Type');\n }\n\n public function syncProfiles(?User $userToSearch = null): ?Profile\n {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, ['profile' => $this->profile]);\n $query = $queryBuilder->buildGetUsersQuery($userToSearch);\n\n try {\n $salesforceUsers = $this->queryHandler->query($query, [\n 'active' => true,\n ]);\n } catch (NoResultsException $e) {\n $this->logger->info('[Salesforce] Sync Profiles. No users found', [\n 'query' => $query,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $teamRepository = app(TeamRepository::class);\n $customRules = $this->getCustomProfileRules($teamRepository);\n\n foreach ($salesforceUsers as $crmUser) {\n if ($crmUser['Email'] === null) {\n continue;\n }\n\n if (! $this->customProfileValidation($crmUser, $customRules)) {\n continue;\n }\n\n $user = $teamRepository->findActiveTeamMemberByEmail($this->team, $crmUser['Email']);\n\n if (! $user instanceof User) {\n continue;\n }\n\n $edition = $crmUser['UserPreferencesLightningExperiencePreferred']\n ? Profile::EDITION_LIGHTNING\n : Profile::EDITION_CLASSIC;\n\n $profileRepository = app(ProfileRepository::class);\n $profile = $profileRepository->updateOrCreateProfile(\n $user,\n [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmUser['Id'],\n ],\n [\n 'user_id' => $user->getId(),\n 'edition' => $edition,\n 'has_external_cti' => ! empty($crmUser['CallCenterId']),\n 'crm_profile_id' => $crmUser['ProfileId'],\n ]\n );\n\n if ($userToSearch instanceof User && $userToSearch->getId() === $user->getId()) {\n return $profile;\n }\n }\n\n // Clean up inactive profiles\n try {\n $this->archiveInactiveProfiles();\n } catch (\\Exception $e) {\n $this->logger->warning('[Salesforce] Profile archiving failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $e->getMessage(),\n ]);\n }\n\n return null;\n }\n\n public function generateProviderUrl(string $providerId, string $objectType): ?string\n {\n $url = null;\n\n // For Salesforce it's easy, we just point every object to the apex domain and they handle it.\n switch ($objectType) {\n case 'lead':\n case 'account':\n case 'contact':\n case 'opportunity':\n case 'task':\n case 'event':\n case 'activity':\n\n $url = $this->config->crm_base_url . '/' . $providerId;\n\n break;\n }\n\n return $url;\n }\n\n public function buildTaskSearchFields(): array\n {\n return ['Id', 'WhoId', 'WhatId', 'AccountId'];\n }\n\n public function getTaskByFilterConditions(\n array $fields,\n array $filters,\n bool $bulkSearch = false,\n bool $strictFilters = true\n ): ?array {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $query = $queryBuilder->buildSearchTaskQuery($fields, $filters, $bulkSearch, $strictFilters);\n\n try {\n if (! $bulkSearch) {\n $objects = $this->queryHandler->query($query, $filters);\n if ($objects->count() === 1) {\n return $objects->current();\n }\n }\n\n if ($bulkSearch) {\n $objects = $this->queryHandler->query($query);\n $records = [];\n foreach ($objects as $record) {\n $key = $record[end($fields)];\n $records[$key] = $record;\n }\n\n return $records;\n }\n } catch (\\Exception $e) {\n $this->logger->info('[Salesforce] Failed to execute query', [\n 'query' => $query,\n 'error' => $e->getMessage(),\n ]);\n }\n\n return null;\n }\n\n public function mapCrmObjects(array $task): array\n {\n $activityData = [];\n\n if (! empty($task['WhoId'])) {\n $type = $this->parseObjectType($task['WhoId']);\n $activityData[$type] = $task['WhoId'];\n }\n if (! empty($task['AccountId'])) {\n $activityData['account'] = $task['AccountId'];\n }\n if (! empty($task['WhatId'])) {\n $activityData['opportunity'] = $task['WhatId'];\n }\n\n return $activityData;\n }\n\n /**\n * Get SF task by Outreach call id.\n */\n public function getTaskByFilter(\n string $activityFieldType,\n array $filters,\n string $operator = '=',\n array $additionalFields = []\n ): ?array {\n $data = [];\n\n try {\n // Default (base) fields.\n $fields = ['Id', 'Subject', 'Description', 'ActivityDate', 'WhoId', 'WhatId', $activityFieldType];\n\n foreach ($additionalFields as $additionalField) {\n $fields[] = $additionalField->crm_provider_id;\n }\n\n $fields = array_unique($fields);\n\n // Find task with the same Outreach id as the call id.\n $query = 'SELECT ' . implode(',', $fields) . '\n FROM Task\n WHERE IsArchived = false AND IsDeleted = false';\n\n foreach ($filters as $key => $value) {\n $key = preg_quote($key, '/');\n $key = str_replace(['\\'', '\"'], '', $key);\n // Prepare the substitution.\n $strKey = \":$key\";\n\n $query .= \" AND $key $operator $strKey\";\n }\n\n $query .= ' ORDER BY LastModifiedDate DESC LIMIT 1';\n\n $objects = $this->queryHandler->query($query, $filters);\n\n // There should be only one task related to this call if any.\n if ($objects->count() === 1) {\n $object = $objects->current();\n\n $dueDate = $object['ActivityDate'] ? Carbon::parse($object['ActivityDate'])->toIso8601String() : null;\n\n $data = array_merge($object, [\n 'crmId' => $object['Id'],\n 'subject' => $object['Subject'],\n 'summary' => $object['Description'],\n 'due' => $dueDate,\n 'Type' => $object[$activityFieldType],\n ]);\n }\n } catch (NoResultsException $e) {\n // Filters don't match any records.\n } catch (ServiceUnavailableException $serviceUnavailableException) {\n // Service cannot be queried. We should probably log this.\n }\n\n return $data;\n }\n\n /**\n * Get Salesforce fields including datetime fields\n *\n * @param $objectType\n */\n private function getAllFieldsAsArray($objectType): array\n {\n $basicFields = [];\n // Not all users have access to all object fields.\n if ($this->profile->{$objectType . '_fields'}) {\n $basicFields = explode(',', $this->profile->{$objectType . '_fields'});\n }\n\n $extraFields = [\n 'CreatedDate',\n 'LastModifiedDate',\n 'IsDeleted',\n ];\n\n if ($objectType === self::OBJECT_OPPORTUNITY\n && $this->config->opportunity_value_field_id\n && ! in_array($this->config->opportunityValueField->crm_provider_id, $basicFields)\n ) {\n $extraFields[] = $this->config->opportunityValueField->crm_provider_id;\n }\n\n return array_unique(array_merge($basicFields, $extraFields));\n }\n\n /**\n * Generate transcription for activity description.\n */\n private function generateTranscription(Activity $activity): string\n {\n if (! ($this->config->store_transcript)) {\n // If sending transcription to activity toggle is disabled\n return '';\n }\n\n return $this->transcriptionService\n ->findTranscriptionByActivity($activity)\n ->map(static function (array $transcriptionSegment): string {\n return $transcriptionSegment['formattedStartsAt'] . ' | ' . $transcriptionSegment['transcript'];\n })\n ->implode(PHP_EOL);\n }\n\n /**\n * Find related Salesforce event based on activity data\n *\n * @return array<string>\n */\n public function fetchRelatedActivity(Activity $activity): array\n {\n $this->logger->info('[Salesforce] Searching for related activity', [\n 'activityId' => $activity->getUuid(),\n 'ownerId' => $this->profile?->crm_provider_id,\n ]);\n\n $sfEvent = $this->fetchRelatedEvent($activity);\n if (empty($sfEvent)) {\n $this->logger->info('[Salesforce] No related activity found', [\n 'activityId' => $activity->getUuid(),\n 'ownerId' => $this->profile?->crm_provider_id,\n 'account' => $activity->hasAccount()\n ? $activity->getAccount()->getCrmProviderId()\n : null,\n ]);\n\n return [];\n }\n\n return $sfEvent;\n }\n\n public function fetchAndAssociateRelatedActivity(Activity $activity): ?Activity\n {\n if ($activity->isTypeConference() === false) {\n return null;\n }\n\n if ($activity->hasActualStartTime() === false && $activity->hasScheduledStartTime() === false) {\n return null;\n }\n\n if (! $activity->hasProspect()) {\n $this->logger->info('[Salesforce] Skip look up, Activity not linked to Lead, Contact or Account', [\n 'activityId' => $activity->getUuid(),\n ]);\n\n return null;\n }\n\n $playbook = $this->getPlaybook($activity->getUser());\n if ($playbook !== null && $playbook->getActivityType() === Playbook::ACTIVITY_TYPE_TASK) {\n $this->logger->info('[Salesforce] Skip auto-sync for task-based playbook', [\n 'activityUuid' => $activity->getUuid(),\n 'playbookId' => $playbook->getId(),\n 'playbookType' => $playbook->getActivityType(),\n ]);\n\n return null;\n }\n\n try {\n $sfEvent = $this->fetchRelatedActivity($activity);\n if (empty($sfEvent)) {\n return null;\n }\n\n [$activityField, $activityType] = $this->resolveActivityTypeFromEvent($activity, $sfEvent);\n\n $this->logger->info('[Salesforce] Found related activity', [\n 'activityId' => $activity->getUuid(),\n 'sfEvent' => $sfEvent['Id'],\n 'activityFieldName' => $activityField,\n 'crmActivityType' => ($activityField !== null && isset($sfEvent[$activityField]))\n ? $sfEvent[$activityField]\n : null,\n 'activityType' => $activityType,\n ]);\n\n $userId = $this->findRelatedActivityUserId($activity, $sfEvent);\n\n if ($activity->getUserId() !== $userId) {\n $this->logger->info('[Salesforce] Updating meeting owner', [\n 'activityId' => $activity->getUuid(),\n 'oldUserId' => $activity->getUserId(),\n 'newUserId' => $userId,\n ]);\n }\n\n $this->updateSfEventDescription($activity, $sfEvent);\n\n $activity->update([\n 'user_id' => $userId,\n 'crm_provider_id' => $sfEvent['Id'],\n 'playbook_category_id' => $activityType->id ?? $activity->getCategory()?->getId(),\n ]);\n\n $this->logger->info('[Salesforce] Activity updated', [\n 'activityId' => $activity->getUuid(),\n ]);\n\n return $activity;\n } catch (\\Exception $exception) {\n \\Sentry::captureException($exception);\n\n throw $exception;\n }\n }\n\n /**\n * @param array<string, mixed> $sfEvent\n *\n * @return array{0: string|null, 1: mixed}\n */\n private function resolveActivityTypeFromEvent(Activity $activity, array $sfEvent): array\n {\n $activityField = $this->getActivityFieldName($activity);\n $activityType = null;\n\n if ($activityField !== null && ! empty($sfEvent[$activityField])) {\n $playbook = $this->getPlaybook($activity->getUser());\n $activityType = $this->getPlaybookCategory($playbook, strval($sfEvent[$activityField]));\n }\n\n return [$activityField, $activityType];\n }\n\n /**\n * @param array<string> $sfEvent\n */\n private function findRelatedActivityUserId(Activity $activity, array $sfEvent): int\n {\n $userId = $activity->getUserId();\n\n if (empty($sfEvent['OwnerId']) === false) {\n $profile = $this\n ->config\n ->profiles()\n ->where('crm_provider_id', $sfEvent['OwnerId'])\n ->get()\n ->filter(static function (Profile $profile) use ($activity): bool {\n if (! $activity->isTypeConference()) {\n return ! empty($profile->user) ? $profile->user->isStatusActive() : false;\n }\n\n $participants = $activity->getParticipants();\n\n return ! empty($profile->user)\n ? $profile->user->isStatusActive()\n && $profile->user->hasPermission(PermissionEnum::RECORD_MEETING)\n && $participants->contains('user_id', $profile->user_id)\n : false;\n })\n ->first();\n\n if ($profile) {\n $userId = $profile->user_id;\n }\n }\n\n return $userId;\n }\n\n /**\n * @param array<string, mixed> $sfEvent\n */\n private function updateSfEventDescription(Activity $activity, array $sfEvent): void\n {\n try {\n if (str_contains($sfEvent['Description'], $activity->id_string)) {\n return;\n }\n\n $payload = [\n 'Description' => $sfEvent['Description']\n . PHP_EOL\n . PHP_EOL\n . (new DecorateActivity())->generateDescription($activity),\n ];\n\n $this->logger->info('[Salesforce] Update record', [\n 'activityId' => $activity->getUuid(),\n 'sfEvent' => $sfEvent['Id'],\n 'payload' => $payload,\n ]);\n\n $payload = array_merge($payload, $this->fetchCustomFieldData($activity, Field::OBJECT_EVENT));\n\n $this->updateRecord('Event', $sfEvent['Id'], $payload);\n } catch (\\Exception) {\n $this->logger->error('[Salesforce] Failed to update record', [\n 'activityUuid' => $activity->getUuid(),\n 'sfEvent' => $sfEvent['Id'],\n ]);\n }\n }\n\n /**\n * Returns the most recently modified Event within time range (if any).\n *\n * @return array|null An Event record from Salesforce.\n */\n private function fetchRelatedEvent(Activity $activity): ?array\n {\n $ownerId = $this->profile?->crm_provider_id;\n if ($ownerId === null) {\n return [];\n }\n\n /** @var ?Carbon $from */\n /** @var ?Carbon $to */\n [$from, $to] = $this->getFromToDates($activity);\n\n try {\n $whoId = null;\n $hasWho = $activity->lead_id || $activity->contact_id;\n if ($hasWho) {\n $whoId = $activity->hasLead()\n ? $activity->getLead()->crm_provider_id\n : $activity->getContact()->crm_provider_id;\n }\n\n if ($hasWho === false && $activity->account_id === null) {\n return null;\n }\n\n $query = $this->buildFetchRelatedEventQuery($activity);\n\n $objects = $this->queryHandler->query($query, [\n 'ownerId' => $ownerId,\n 'whoId' => $whoId,\n 'whatId' => $activity->hasOpportunity() ? $activity->getOpportunity()->crm_provider_id : null,\n 'accountId' => $activity->hasAccount() ? $activity->getAccount()->crm_provider_id : null,\n 'from' => $from?->format('Y-m-d\\TH:i:s\\Z'),\n 'to' => $to?->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($objects as $object) {\n return $object;\n }\n } catch (NoResultsException $e) {\n return [];\n }\n\n return [];\n }\n\n private function getFromToDates(Activity $activity): array\n {\n $from = null;\n $to = null;\n\n /** @var ?CalendarEvent $calendarEvent */\n $calendarEvent = $activity->calendarEvent()->first();\n if ($calendarEvent !== null) {\n $from = $calendarEvent->getStartTime();\n $to = $calendarEvent->getEndTime();\n }\n\n // For non-calendar imported activities\n // Also double check if calendar event dates could be null?\n // If null use what we've got so far\n if ($from === null || $to === null) {\n $from = $activity->hasScheduledStartTime()\n ? $activity->getScheduledStartTime()\n : $activity->getActualStartTime();\n $to = $activity->hasScheduledEndTime()\n ? $activity->getScheduledEndTime()->addMinutes(15)\n : $activity->getActualEndTime();\n }\n\n return [$from, $to];\n }\n\n /**\n * Determines the appropriate activity field name for querying Salesforce events.\n *\n * This method follows a hierarchy to determine the field name:\n * 1. Uses the playbook's activity field if it exists and is in the profile's accessible fields\n * 2. Falls back to the default activity field if the profile has no event fields configured\n * 3. Returns null if no suitable field is found\n *\n * @param Activity $activity The activity to determine the field for\n *\n * @return string|null The field name to use in queries, or null if none is available\n */\n private function getActivityFieldName(Activity $activity): ?string\n {\n if ($this->profile === null) {\n $this->logger->warning('[Salesforce] Cannot determine activity field - profile not found', [\n 'activityId' => $activity->getUuid(),\n ]);\n\n return null;\n }\n\n $profileEventFields = $this->profile->getFieldsAsArray('event');\n\n if (empty($profileEventFields)) {\n $defaultActivityField = $this->getDefaultActivityField(Field::OBJECT_EVENT);\n $defaultFieldName = $defaultActivityField?->getAttribute('crm_provider_id');\n // Profile not yet synced — fall back to the default activity field.\n // There is a small chance that the profile won't have Default Activity Type field access\n // in which case the query will fail.\n // This is however an edge case and should be reviewed for profile sync issues.\n Sentry::withScope(function (\\Sentry\\State\\Scope $scope) use ($defaultFieldName): void {\n $scope->setContext('details', [\n 'profileId' => $this->profile->id,\n 'defaultField' => $defaultFieldName,\n ]);\n Sentry::captureMessage(\n '[Salesforce] Profile event fields empty, falling back to default activity field.',\n \\Sentry\\Severity::warning()\n );\n });\n\n return $defaultFieldName;\n }\n\n $playbook = $this->getPlaybook($activity->getUser());\n\n if (! is_null($playbook) && ! is_null($playbook->getActivityField())) {\n $playbookFieldName = $playbook->getActivityField()->getAttribute('crm_provider_id');\n\n if (in_array($playbookFieldName, $profileEventFields, true)) {\n return $playbookFieldName;\n }\n\n $this->logger->warning('[Salesforce] Playbook activity field not found in profile fields', [\n 'activityId' => $activity->getUuid(),\n 'playbookField' => $playbookFieldName,\n 'profileId' => $this->profile->id,\n ]);\n }\n\n return null;\n }\n\n private function buildFetchRelatedEventQuery(Activity $activity): string\n {\n $hasWho = $activity->lead_id || $activity->contact_id;\n\n $activityFieldName = $this->getActivityFieldName($activity);\n $fields = array_filter(['Id', 'Description', 'OwnerId', $activityFieldName]);\n\n $ownerCondition = '(OwnerId = :ownerId OR CreatedById = :ownerId)';\n\n $query = '\n SELECT ' . implode(',', $fields) . '\n FROM Event\n WHERE ' . $ownerCondition . '\n AND IsArchived = false\n AND IsAllDayEvent = false\n AND StartDateTime >= :from\n AND EndDateTime <= :to\n AND (';\n\n $operator = '';\n if ($activity->account_id) {\n // This covers events tied to a related contact or opportunity too.\n $query .= 'AccountId = :accountId';\n\n $operator = ' OR ';\n }\n\n if ($hasWho) {\n $query .= $operator . 'WhoId = :whoId';\n\n // If we are also going to check on a specific opportunity, set that up.\n if ($activity->opportunity_id) {\n $query .= ' OR WhatId = :whatId';\n }\n }\n\n $query .= ') ORDER BY LastModifiedDate DESC';\n\n return $query;\n }\n\n public function fetchProspect(array $task): array\n {\n $lead = $account = $opportunity = $contact = $stage = $countryCode = null;\n $externalId = $task['WhoId'] ?? null;\n\n // Lead or Contact\n if ($externalId) {\n try {\n [$lead, $account, $opportunity, $contact, $stage, $countryCode] = $this->parseRecords($externalId);\n } catch (\\InvalidArgumentException $exception) {\n // Invalid object type.\n }\n }\n\n // If we happen to know the opportunity or account from the Task, figure that out.\n if (empty($task['WhatId']) === false) {\n // WhatId could be either Account ID or Opportunity ID.\n // If WhatId is Opportunity ID, get the opportunity and stage from the CRM.\n try {\n [, $account, $opportunity, , $stage, ] = $this->parseRecords($task['WhatId']);\n } catch (\\InvalidArgumentException $exception) {\n // Invalid object type.\n }\n }\n\n return [$lead, $account, $opportunity, $contact, $stage, $countryCode];\n }\n\n /**\n * Save activity transcription summary as note\n */\n public function saveTranscriptionSummaryAsNote(\n ActivityContract $activity,\n string $title,\n string $body,\n ?string $objectId,\n ?NoteObject $noteObject = null,\n ): ?string {\n return $this->saveNote($title, $body, (string) $objectId);\n }\n\n public function getObjectByFilterConditions(string $objectType, array $fields, array $filters): ?array\n {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $query = $queryBuilder->buildObjectSearchQuery($objectType, $fields, $filters);\n\n try {\n $objects = $this->queryHandler->query($query, $filters);\n if ($objects->count() === 1) {\n return $objects->current();\n }\n } catch (\\Exception $e) {\n $this->logger->info('[Salesforce] Failed to execute query', [\n 'query' => $query,\n 'error' => $e->getMessage(),\n ]);\n }\n\n return null;\n }\n\n private function getCustomProfileRules(TeamRepository $teamRepository): array\n {\n $teamSettings = $teamRepository->getTeamSetting($this->team, 'custom_profile_validation');\n\n if ($teamSettings instanceof TeamSettings && $teamSettings->getValueType() === 'array') {\n $customRules = json_decode($teamSettings->getValue(), true);\n if (is_array($customRules)) {\n return $customRules;\n }\n }\n\n return [];\n }\n\n private function customProfileValidation(array $crmUser, array $customRules): bool\n {\n foreach ($customRules as $customRule) {\n if ($crmUser[$customRule['field']] !== $customRule['value']) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * When syncing Contact / Lead / Account / Opportunity / Stage crm entities,\n * validate and restore locally trashed objects,\n * before updating them. Objects are identified by CrmProviderId\n */\n private function restoreAnyTrashedEntity(HasMany $targetEntity, string $crmProviderId): void\n {\n $recordExists = $targetEntity->withTrashed()->where(['crm_provider_id' => $crmProviderId])->first();\n if ($recordExists && $recordExists->trashed()) {\n $recordExists->restore();\n }\n }\n\n #[\\Override] public function supportsNotes(): bool\n {\n return true;\n }\n\n private function getOwnerProfile(?string $ownerId): ?Profile\n {\n if ($ownerId === null) {\n return null;\n }\n\n return $this->config->profiles()\n ->where('crm_provider_id', $ownerId)\n ->first();\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Services\\Crm\\Salesforce;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Component\\Country\\CountriesMap;\nuse Jiminny\\Contracts\\Acl\\PermissionEnum;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Services\\Crm\\FetchRelatedActivityInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\LayoutManagementInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\MatchCrmEntitiesInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\Provider\\SalesforceBatchSyncInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\Provider\\SalesforceInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\RemoteEntityLookupInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\RemoteEntityManipulationInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\RemoteNoteEntityManipulationInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SearchTaskInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SendSummaryToCrmInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SettingsInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SupportsObjectTypeParseInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SyncCrmEntitiesInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\SyncCrmProfileRecordTypesInterface;\nuse Jiminny\\Contracts\\Services\\Crm\\VerifyTaskExistsInterface;\nuse Jiminny\\Enums\\CrmObject;\nuse Jiminny\\Events\\Activities\\Crm\\LeadConverted;\nuse Jiminny\\Events\\Activities\\Crm\\ActivityLeadConverted;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Exceptions\\HttpBadRequestException;\nuse Jiminny\\Exceptions\\HttpNotFoundException;\nuse Jiminny\\Exceptions\\NoResultsException;\nuse Jiminny\\Exceptions\\ServiceUnavailableException;\nuse Jiminny\\Jobs\\Crm\\NoteObject;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Calendar\\CalendarEvent;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Contracts\\ActivityContract;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Models\\Crm\\Configuration;\nuse Jiminny\\Models\\Crm\\ContactRole;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\Profile;\nuse Jiminny\\Models\\Crm\\RecordType;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Models\\Opportunity;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\SocialAccount;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Models\\TeamSettings;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\Crm\\ContactRoleRepository;\nuse Jiminny\\Repositories\\Crm\\FieldDataRepository;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\Crm\\ProfileRepository;\nuse Jiminny\\Repositories\\Crm\\RecordTypeFieldValuesRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\BaseService;\nuse Jiminny\\Services\\Crm\\Helpers\\ArrayIterator;\nuse Jiminny\\Services\\Crm\\MatchDomainByEmailInterface;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Services\\Crm\\ResolveCompanyNameByEmailTrait;\nuse Jiminny\\Services\\Crm\\Salesforce\\Fields\\FieldTypeConverter;\nuse Jiminny\\Services\\Crm\\Salesforce\\Fields\\ValueNormalizer;\nuse Jiminny\\Services\\Crm\\Salesforce\\ServiceTraits\\RecordManipulationsTrait;\nuse Jiminny\\Services\\Crm\\Salesforce\\ServiceTraits\\SyncFieldsTrait;\nuse Jiminny\\Utils\\CurrencyFormatter;\nuse Jiminny\\Utils\\StringUtil;\nuse Ramsey\\Uuid\\Uuid;\nuse Sentry\\Laravel\\Facade as Sentry;\n\nclass Service extends BaseService implements\n SalesforceInterface,\n SalesforceBatchSyncInterface,\n SyncCrmEntitiesInterface,\n SyncCrmProfileRecordTypesInterface,\n RemoteEntityManipulationInterface,\n FetchRelatedActivityInterface,\n SendSummaryToCrmInterface,\n MatchDomainByEmailInterface,\n SearchTaskInterface,\n LayoutManagementInterface,\n SettingsInterface,\n MatchCrmEntitiesInterface,\n RemoteEntityLookupInterface,\n SupportsObjectTypeParseInterface,\n RemoteNoteEntityManipulationInterface,\n VerifyTaskExistsInterface\n{\n use ResolveCompanyNameByEmailTrait;\n use SyncFieldsTrait;\n use DeleteObjectsTrait;\n use RecordManipulationsTrait;\n use ServiceTraits\\BatchSyncTrait;\n\n /**\n * Note Body Limit for the Old Note-Taking Tool\n *\n * @var int\n */\n private const int CLASSIC_NOTE_MAX_LENGTH = 32000;\n\n /**\n * Note Content Limit for the New Notes\n *\n * @var int\n */\n private const int ENHANCED_NOTE_MAX_LENGTH = 50000000;\n\n private const string INSTALLED_PACKAGE_ID = '033Tw0000007bKbIAI';\n\n private const int CACHE_TTL = 600;\n\n private const int TASK_VERIFICATION_CACHE_TTL = 86400; // 1 day - 86400\n\n /**\n * @var Client\n */\n protected $client;\n\n private PayloadBuilder $payloadBuilder;\n private QueryHandler $queryHandler;\n\n private OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n\n public function __construct(\n Client $client,\n PayloadBuilder $payloadBuilder,\n private readonly CountriesMap $countriesMap,\n private readonly ProspectPhotoPathService $prospectPhotoPathService,\n ) {\n parent::__construct();\n\n $this->client = $client;\n $this->payloadBuilder = $payloadBuilder;\n $this->queryHandler = app(QueryHandler::class, [\n 'client' => $this->client,\n 'logger' => $this->logger,\n ]);\n $this->opportunitySyncStrategyResolver = app(OpportunitySyncStrategyResolver::class, [\n 'client' => $this->client,\n ]);\n }\n\n public function getDisplayName(): string\n {\n return 'Salesforce';\n }\n\n public function getJobDelay(): int\n {\n return 1;\n }\n\n protected function getOAuthAccount(User $user): ?SocialAccount\n {\n return $user->getSocialAccount(SocialAccount::PROVIDER_SALESFORCE);\n }\n\n public function verifyTaskExists(Activity $activity): bool\n {\n $crmProviderId = $activity->getCrmProviderId();\n $cacheKey = \"crm_task_exists:{$this->config->getId()}:$crmProviderId\";\n\n return Cache::remember($cacheKey, self::TASK_VERIFICATION_CACHE_TTL, function () use ($activity, $crmProviderId) {\n $playbook = $this->getPlaybookFromActivity($activity);\n\n if ($playbook === null) {\n $this->logger->warning('[Salesforce] Cannot verify task - no playbook found', [\n 'activity' => $activity->getId(),\n 'crm_provider_id' => $crmProviderId,\n ]);\n\n return false;\n }\n\n $objectType = $playbook->getActivityType() === Playbook::ACTIVITY_TYPE_EVENT ? 'Event' : 'Task';\n\n try {\n $record = $this->getRecord($objectType, $crmProviderId, ['Id', 'IsDeleted']);\n\n return ! empty($record) && ($record['IsDeleted'] ?? false) === false;\n } catch (HttpNotFoundException|HttpBadRequestException) {\n $this->logger->info('[Salesforce] Activity record not found during verification', [\n 'activity' => $activity->getId(),\n 'object_type' => $objectType,\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return false;\n }\n });\n }\n\n public function query(string $queryToRun, array $parameters = []): QueryIterator\n {\n // Due to poorly designed external calls, this method cannot be entirely removed\n return $this->queryHandler->query($queryToRun, $parameters);\n }\n\n /*=========== Organization Information ===============*/\n\n /**\n * Get a list of all the API Versions for the instance.\n *\n * @throws CrmException\n *\n * @return mixed\n *\n */\n public function getApiVersions()\n {\n $url = $this->config->crm_base_url . '/services/data';\n\n $response = $this->client->get($url);\n\n return json_decode($response->getBody(), true);\n }\n\n\n /**\n * Gets the valid recordTypes for a given Salesforce Object via the describe API.\n *\n * @param string $crmObject The name of the Salesforce object. i.e. Account or Contact\n *\n * @return array The API output, converted from JSON to an associative array.\n */\n public function getRecordTypes(string $crmObject): array\n {\n $url = $this->client->getObjectsUrl() . $crmObject . '/describe';\n\n $response = $this->client->get($url);\n $jsonResponse = json_decode($response->getBody(), true);\n\n $fields = [];\n foreach ($jsonResponse['recordTypeInfos'] as $row) {\n $fields[] = ['recordTypeId' => $row['recordTypeId'], 'default' => $row['defaultRecordTypeMapping']];\n }\n\n return $fields;\n }\n\n /**\n * Convert raw field data into a format compatible with CRM APIs.\n */\n public function normalizeValue(string $fieldType, string $fieldValue, bool $internal = false): string\n {\n return ValueNormalizer::normalize($fieldType, $fieldValue, $internal);\n }\n\n /**\n * @inheritdoc\n */\n public function getDefaultFields(string $activityType): array\n {\n $fields = [];\n\n $defaultFields = ($activityType === Playbook::ACTIVITY_TYPE_TASK)\n ? FieldDefinitions::defaultTaskFields()\n : FieldDefinitions::defaultEventFields();\n\n // This lazy creates these fields if not already setup.\n foreach ($defaultFields as $defaultField) {\n $fields[] = $this->config->fields()->firstOrCreate($defaultField);\n }\n\n return $fields;\n }\n\n /**\n * @inheritdoc\n */\n public function getDefaultActivityField(string $activityType): Field\n {\n // Setup the activity field as the default Type.\n /** @var Field $activityField */\n $activityField = $this->config->fields()->where([\n 'crm_provider_id' => 'Type',\n 'object_type' => $activityType,\n ])->first();\n\n return $activityField;\n }\n\n /**\n * @inheritdoc\n */\n public function getSupportedPlaybookTypes(): array\n {\n return [Playbook::ACTIVITY_TYPE_TASK, Playbook::ACTIVITY_TYPE_EVENT];\n }\n\n protected function getDefaultFollowupLayoutFields(string $activityType): array\n {\n $fields = [];\n $fieldRepo = app(FieldRepository::class);\n\n $fieldFilter = ($activityType === Playbook::ACTIVITY_TYPE_TASK)\n ? FieldDefinitions::taskFollowupFieldsFilter()\n : FieldDefinitions::eventFollowupFieldsFilter();\n\n foreach ($fieldFilter as $eachFilter) {\n $field = $fieldRepo->findOneConfigurationFieldByProperties($this->config, $eachFilter);\n\n // Only add the field if it is created, which it should be.\n if ($field) {\n $fields[] = $field;\n }\n }\n\n return $fields;\n }\n\n public function getDealInsightsFields(): array\n {\n return FieldDefinitions::dealInsightsFields();\n }\n\n private function isCustomField(Field $field): bool\n {\n return substr($field->crm_provider_id, -\\strlen('__c')) === '__c';\n }\n\n /**\n * This one is now called only when ImportActivityTypes is triggered or SyncFieldMetadata executed manually\n * Regular sync now uses SharedSyncFieldsTrait -> syncSingleObjectType\n * Needs to be replaced later on\n */\n public function syncField(Field $field): void\n {\n try {\n if ($this->isCustomField($field)) {\n $query = '\n SELECT\n Id, Metadata, TableEnumOrId\n FROM\n CustomField\n WHERE\n DeveloperName = :fieldName\n AND\n TableEnumOrId = :fieldType\n AND\n NamespacePrefix = :namespacePrefix';\n\n // We need to constrain the field lookup to the object, in case it's used in multiple places.\n $objectType = \\in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true)\n ? 'activity'\n : $field->object_type;\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'fieldName' => substr($field->crm_provider_id, 0, -\\strlen('__c')),\n 'fieldType' => ucfirst($objectType),\n\n // This is used to ensure we only consider the field within the org, not installed packages.\n 'namespacePrefix' => 'null',\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n // Sync field metadata.\n $metadata = $sfField['Metadata'];\n\n $field->description = mb_strimwidth($metadata['description'] ?? '', 0, 191);\n $field->label = mb_strimwidth($metadata['label'] ?? '', 0, Field::LABEL_MAX_LENGTH);\n $field->type = $this->convertFieldType($metadata['type'], $field->getEntityName());\n $field->is_mandatory = ($metadata['required'] === true);\n $field->length = $metadata['length'];\n $field->default_value = mb_strimwidth(trim($metadata['defaultValue'] ?? '', '\"'), 0, 191);\n $field->save();\n } else {\n $query = '\n SELECT\n Id, DataType, DeveloperName, Label, Length, Description\n FROM\n FieldDefinition\n WHERE\n DurableId = :entityName';\n\n $entityName = $field->getEntityName();\n $sfFields = $this->queryHandler->metadata($query, [\n 'entityName' => $entityName,\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n $convertedType = $this->convertFieldType($sfField['DataType'], $entityName);\n $label = mb_strimwidth($sfField['Label'], 0, Field::LABEL_MAX_LENGTH);\n\n if ($field->isBusinessType()) {\n $label = 'Opportunity Type';\n }\n\n $field->description = mb_strimwidth($sfField['Description'], 0, Field::DESCRIPTION_MAX_LENGTH);\n $field->label = $label;\n $field->type = $convertedType;\n $field->length = $sfField['Length'];\n $field->save();\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n private function convertFieldType(string $from, ?string $entityName = null): string\n {\n $converter = new FieldTypeConverter();\n\n return $converter->convert($from, $entityName);\n }\n\n /**\n * @inheritdoc\n */\n public function importPicklistValues(Field $field): array\n {\n $values = [];\n $fieldValues = [];\n\n try {\n if ($this->isCustomField($field)) {\n $query = '\n SELECT\n Id, Metadata, TableEnumOrId\n FROM\n CustomField\n WHERE\n DeveloperName = :fieldName\n AND\n TableEnumOrId = :fieldType\n AND\n NamespacePrefix = :namespacePrefix';\n\n // We need to constrain the field lookup to the object, in case it's used in multiple places.\n $objectType = \\in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true) ?\n 'activity' : $field->object_type;\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'fieldName' => substr($field->crm_provider_id, 0, -\\strlen('__c')),\n 'fieldType' => ucfirst($objectType),\n // This is used to ensure we only consider the field within the org, not installed packages.\n 'namespacePrefix' => 'null',\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n $valueSet = $sfField['Metadata']['valueSet'];\n\n if ($valueSet['valueSetName'] === null) {\n // Local picklist values can be obtained easily.\n $picklistValues = $valueSet['valueSetDefinition']['value'];\n } else {\n // But for some fields, we just get the Global Value Picklist pointer so need to do more work.\n $picklistValues = $this->importGlobalValuePicklistValues($valueSet['valueSetName']);\n }\n\n // Import all active values.\n foreach ($picklistValues as $i => $sfFieldValue) {\n // Setup default value.\n if ($sfFieldValue['default']) {\n $field->update(['default_value' => $sfFieldValue['valueName']]);\n }\n\n // This comes through as null if active (lol).\n if ($sfFieldValue['isActive'] !== false) {\n $values[] = [\n 'value' => $sfFieldValue['valueName'],\n 'label' => $sfFieldValue['valueName'],\n 'sequence' => $i,\n 'is_default' => $sfFieldValue['default'],\n ];\n }\n }\n } else {\n $objectFields = $this->getObjectFields($field->object_type);\n $fieldId = $field->crm_provider_id;\n\n // Only work with our field of interest.\n $objectField = array_filter($objectFields, function ($item) use ($fieldId) {\n return $item['name'] === $fieldId;\n });\n\n $objectField = array_shift($objectField);\n if (empty($objectField['picklistValues']) === false) {\n foreach ($objectField['picklistValues'] as $i => $sfFieldValue) {\n // Skip inactive values.\n if ($sfFieldValue['active'] === false) {\n continue;\n }\n\n // Setup default value.\n if ($sfFieldValue['defaultValue']) {\n $field->update(['default_value' => $sfFieldValue['value']]);\n }\n\n $values[] = [\n 'value' => $sfFieldValue['value'],\n 'label' => $sfFieldValue['label'],\n 'sequence' => $i,\n 'is_default' => $sfFieldValue['defaultValue'],\n ];\n }\n }\n }\n\n $fieldsToPurge = $field->values()->get()->pluck('value')->toArray();\n\n foreach ($values as $value) {\n $value['value'] = substr($value['value'] ?? '', 0, 255);\n $fieldValues[] = $field->values()->updateOrCreate([\n 'value' => $value['value'],\n ], $value);\n\n // Remove this value from the ones we are going to purge.\n if (($key = array_search($value['value'], $fieldsToPurge, true)) !== false) {\n unset($fieldsToPurge[$key]);\n }\n }\n\n // Delete the old values that are no longer used.\n // Get IDs of the values to be deleted\n $valuesToDelete = $field->values()->whereIn('value', $fieldsToPurge);\n $valuesToDeleteIds = $valuesToDelete->pluck('id');\n if (! $valuesToDeleteIds->isEmpty()) {\n $recordTypeFieldValuesRepository = app(RecordTypeFieldValuesRepository::class);\n $recordTypeFieldValuesRepository->deleteForCrmFieldValueIds($valuesToDeleteIds->toArray());\n\n // Now safely delete from crm_field_values\n $valuesToDelete->delete();\n }\n\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n return $fieldValues;\n }\n\n /**\n * Gets values from Global Value Picklists.\n */\n private function importGlobalValuePicklistValues(string $picklistName): array\n {\n $query = '\n SELECT\n Metadata\n FROM\n GlobalValueSet\n WHERE\n DeveloperName = :picklistName\n LIMIT 1';\n\n try {\n $sfValues = $this->queryHandler->metadata($query, [\n 'picklistName' => $picklistName,\n ]);\n\n // There is always 1 result at this point.\n $sfValue = $sfValues->current();\n\n return $sfValue['Metadata']['customValue'];\n } catch (NoResultsException $noResultsException) {\n // Nothing returned.\n\n return [];\n }\n }\n\n /**\n * @inheritdoc\n */\n public function syncProfileRecordTypes(): void\n {\n $objectTypes = [\n 'lead',\n 'account',\n 'contact',\n 'opportunity',\n 'task',\n 'event',\n ];\n\n foreach ($objectTypes as $objectType) {\n try {\n $crmRecordTypes = $this->getRecordTypes(ucfirst($objectType));\n\n foreach ($crmRecordTypes as $crmRecordType) {\n // If the record type is default and not the Master type, set this.\n if ($crmRecordType['default'] && $crmRecordType['recordTypeId'] !== '012000000000000AAA') {\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $crmRecordType['recordTypeId'])\n ->first();\n\n if ($recordType) {\n $this->profile->{$objectType . '_record_type_id'} = $recordType->id;\n }\n }\n }\n } catch (HttpNotFoundException $exception) {\n Log::error('No access to ' . $objectType . ' object, skipping...');\n\n // XXX: should we log this fact somewhere?\n continue;\n }\n }\n\n if ($this->profile->isDirty()) {\n $this->profile->save();\n }\n }\n\n /**\n * Gets business processes.\n */\n public function importBusinessProcesses(): void\n {\n $query = '\n SELECT\n Id, IsActive, Name, TableEnumOrId\n FROM\n BusinessProcess\n WHERE\n TableEnumOrId IN (\\'Lead\\',\\'Opportunity\\')';\n\n try {\n $sfProcesses = $this->queryHandler->query($query);\n\n // Upsert all processes for the team.\n foreach ($sfProcesses as $sfProcess) {\n /** @var BusinessProcess $businessProcess */\n $businessProcess = $this->config->businessProcesses()->updateOrCreate([\n 'crm_provider_id' => $sfProcess['Id'],\n ], [\n 'team_id' => $this->team->id,\n 'name' => $sfProcess['Name'],\n 'type' => $sfProcess['TableEnumOrId'] === 'Lead' ? 'lead' : 'opportunity',\n 'is_selectable' => $sfProcess['IsActive'],\n ]);\n\n $this->importBusinessProcessStages($businessProcess);\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n /**\n * Gets business process stages.\n */\n public function importBusinessProcessStages(BusinessProcess $businessProcess): void\n {\n $query = '\n SELECT\n Metadata\n FROM\n BusinessProcess\n WHERE\n Id = :processId';\n\n try {\n $stages = [];\n $sfProcessStages = $this->queryHandler->metadata($query, [\n 'processId' => $businessProcess->crm_provider_id,\n ]);\n\n // There is always 1 result at this point.\n $sfProcessStage = $sfProcessStages->current();\n\n // Upsert all processes for the team.\n foreach ($sfProcessStage['Metadata']['values'] as $sfProcessStage) {\n $sanitizedName = urldecode($sfProcessStage['valueName']); // Must decode: \"%2C\" becomes \",\" etc.\n\n $stage = $businessProcess->crm->stages()\n // This MUST match on label because this API doesn't use API Name.\n ->where('label', $sanitizedName)\n ->where('type', $businessProcess->type)\n ->where('is_selectable', 1)\n ->first();\n\n if ($stage) {\n $stages[] = $stage->id;\n }\n }\n\n $businessProcess->stages()->sync($stages);\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n /**\n * Gets record types.\n */\n public function importRecordTypes(): void\n {\n $query = '\n SELECT\n Id, IsActive, Name, BusinessProcessId, SobjectType\n FROM\n RecordType';\n\n try {\n $sfRecordTypes = $this->queryHandler->query($query);\n\n // Upsert all record types for the process.\n foreach ($sfRecordTypes as $sfRecordType) {\n $businessProcess = null;\n if ($sfRecordType['BusinessProcessId']) {\n $businessProcess = $this->config->businessProcesses()\n ->where('crm_provider_id', $sfRecordType['BusinessProcessId'])\n ->first();\n }\n\n /** @var RecordType $recordType */\n $recordType = $this->config->recordTypes()->updateOrCreate([\n 'crm_provider_id' => $sfRecordType['Id'],\n ], [\n 'team_id' => $this->team->id,\n 'type' => mb_strtolower($sfRecordType['SobjectType']),\n 'name' => $sfRecordType['Name'],\n 'is_selectable' => $sfRecordType['IsActive'],\n 'business_process_id' => $businessProcess->id ?? null,\n ]);\n\n $this->importRecordTypeFieldValues($recordType);\n }\n } catch (NoResultsException $noResultsException) {\n // Do nothing.\n }\n }\n\n /**\n * Import record type - field value mappings. This only works for standard fields.\n */\n public function importRecordTypeFieldValues(RecordType $recordType): void\n {\n try {\n $query = '\n SELECT\n Metadata\n FROM\n RecordType\n WHERE\n Id = :recordTypeId';\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'recordTypeId' => $recordType->crm_provider_id,\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n // Sync field metadata.\n $picklists = $sfField['Metadata']['picklistValues'];\n\n foreach ($picklists as $picklist) {\n $field = $this->config->fields()->where([\n 'type' => Field::TYPE_PICKLIST,\n 'object_type' => $recordType->type,\n 'crm_provider_id' => $picklist['picklist'],\n ])->first();\n\n if ($field) {\n $fieldValues = [];\n\n foreach ($picklist['values'] as $value) {\n // Must decode: \"%2C\" becomes \",\" etc.\n $fieldValue = $field->values()\n ->where('value', urldecode($value['valueName']))\n ->first();\n\n if ($fieldValue) {\n $fieldValues[] = $fieldValue->id;\n }\n }\n\n $recordType->fieldValues()->sync($fieldValues);\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n }\n\n /**\n * @inheritdoc\n */\n public function importStages(?array $types = null, ?string $missingStageName = null): ?Stage\n {\n $params = [];\n $missingStage = null;\n if ($types === null) {\n $types = [Stage::TYPE_LEAD, Stage::TYPE_OPPORTUNITY];\n }\n\n foreach ($types as $type) {\n if ($type === Stage::TYPE_LEAD) {\n $query = '\n SELECT\n Id, ApiName, MasterLabel, SortOrder\n FROM\n LeadStatus';\n } else {\n $query = '\n SELECT\n Id, ApiName, MasterLabel, IsActive, SortOrder, DefaultProbability\n FROM\n OpportunityStage';\n }\n\n if ($missingStageName) {\n $escapedStageName = ValueNormalizer::replaceQueryWithStringLiterals($missingStageName);\n\n $query .= ' WHERE ApiName = :stageName';\n\n $params = [\n 'stageName' => $escapedStageName,\n ];\n }\n\n try {\n $sfStages = $this->queryHandler->query($query, $params);\n } catch (NoResultsException $exception) {\n $sfStages = [];\n }\n\n $missingStage = null;\n\n // Upsert all stages for the team.\n foreach ($sfStages as $sfStage) {\n $selectable = true;\n if (array_key_exists('IsActive', $sfStage)) {\n $selectable = $sfStage['IsActive'];\n }\n\n $this->restoreAnyTrashedEntity($this->config->stages(), $sfStage['Id']);\n\n $stage = $this->config->stages()->updateOrCreate([\n 'crm_provider_id' => $sfStage['Id'],\n ], [\n 'team_id' => $this->team->id,\n 'name' => mb_strimwidth($sfStage['ApiName'], 0, 50),\n 'label' => mb_strimwidth($sfStage['MasterLabel'], 0, 191),\n 'type' => $type,\n 'sequence' => $sfStage['SortOrder'] ?? 0,\n 'is_selectable' => $selectable,\n 'probability' => $sfStage['DefaultProbability'] ?? null,\n ]);\n\n if ($missingStageName && $missingStageName === $sfStage['ApiName']) {\n $missingStage = $stage;\n }\n }\n\n if ($missingStageName && $missingStage === null) {\n // If they requested a stage that still doesn't exist, it must be inactive so lazy create it.\n $missingStage = $this->config->stages()->create([\n 'crm_provider_id' => Uuid::uuid4(),\n 'team_id' => $this->team->id,\n 'name' => mb_strimwidth($missingStageName, 0, 50),\n 'label' => mb_strimwidth($missingStageName, 0, 191),\n 'type' => $type,\n 'sequence' => 0,\n 'is_selectable' => 0,\n ]);\n }\n }\n\n return $missingStage;\n }\n\n /**\n * @inheritdoc\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n $syncCount = 0;\n $fields = $this->getAllFieldsAsArray('lead');\n if (\\in_array('Id', $fields, true) === false) {\n return $syncCount;\n }\n\n $query = '\n SELECT ' . rtrim(implode(',', $fields), ',') . '\n FROM Lead\n WHERE LastModifiedDate > :since\n ORDER BY LastModifiedDate ASC';\n\n try {\n $sfLeads = $this->queryHandler->query($query, [\n 'since' => $since->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($sfLeads as $sfLead) {\n // Only sync if previously imported.\n if ($this->hasLead($sfLead['Id'])) {\n $this->importLead($sfLead);\n $syncCount++;\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::LEAD);\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n $fields = $this->getAllFieldsAsArray('lead');\n\n $sfLead = $this->getRecord('Lead', $crmId, $fields);\n\n return $this->importLead($sfLead);\n }\n\n private function importLead($crmData): ?Lead\n {\n /** @var ?Stage $stage */\n $stage = null;\n if (isset($crmData['Status'])) {\n // Get the current stage.\n $stage = $this->config\n ->stages()\n ->where('name', $crmData['Status'])\n ->where('type', Stage::TYPE_LEAD)\n ->first();\n\n if ($stage === null) {\n // Import it.\n $stage = $this->importStages([Stage::TYPE_LEAD], $crmData['Status']);\n }\n }\n\n // If we have no way of importing this, just return null :(\n if ($stage === null) {\n return null;\n }\n\n $countryCode = $crmData['CountryCode'] ?? null;\n\n // Salesforce allows custom \"countries\" to be created. Disregard these.\n if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {\n $countryCode = null;\n }\n\n // If we have no country code, try to parse it from the country name.\n if ($countryCode === null && empty($crmData['Country']) !== false) {\n $countryCode = $this->convertCountryNameToCode($crmData['Country']);\n }\n\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($crmData['Phone'] ?? '', 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n\n $mobilePhone = null;\n if (empty($crmData['MobilePhone']) === false) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($crmData['MobilePhone'], 0, 25);\n $mobilePhone = phone_e164($countryCode, $number);\n }\n\n $convertedDate = null;\n $convertedAccount = null;\n $convertedOpportunity = null;\n $convertedContact = null;\n\n if ($crmData['IsConverted'] == 'true') {\n $convertedDate = $crmData['ConvertedDate'];\n\n if (empty($crmData['ConvertedAccountId']) === false) {\n $convertedAccount = $this->config\n ->accounts()\n ->where('crm_provider_id', $crmData['ConvertedAccountId'])\n ->first();\n\n if ($convertedAccount === null) {\n try {\n $convertedAccount = $this->syncAccount($crmData['ConvertedAccountId']);\n } catch (HttpNotFoundException $exception) {\n // Probably the user has no permissions to access the converted data.\n }\n }\n }\n\n if (empty($crmData['ConvertedOpportunityId']) === false) {\n $convertedOpportunity = $this->config\n ->opportunities()\n ->where('crm_provider_id', $crmData['ConvertedOpportunityId'])\n ->first();\n\n if ($convertedOpportunity === null) {\n try {\n $convertedOpportunity = $this->syncOpportunity($crmData['ConvertedOpportunityId']);\n } catch (HttpNotFoundException $exception) {\n // Probably the user has no permissions to access the converted data.\n }\n }\n }\n\n if (empty($crmData['ConvertedContactId']) === false) {\n $convertedContact = $this->team\n ->crm\n ->contacts()\n ->where('crm_provider_id', $crmData['ConvertedContactId'])\n ->first();\n\n if ($convertedContact === null) {\n try {\n $convertedContact = $this->syncContact($crmData['ConvertedContactId']);\n } catch (HttpNotFoundException $exception) {\n // Probably the user has no permissions to access the converted data.\n }\n }\n }\n }\n\n if (empty($crmData['Company'])) {\n $company = 'Unknown';\n } else {\n $company = mb_strimwidth($crmData['Company'], 0, 191);\n }\n\n $domain = null;\n if (empty($crmData['Website']) === false) {\n $domain = mb_strimwidth($crmData['Website'], 0, 191);\n $domain = StringUtil::resolveDomain($domain);\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $data = [\n 'team_id' => $this->team->id,\n 'user_id' => $profile?->user_id,\n 'owner_id' => $crmData['OwnerId'] ?? '',\n 'company' => $company,\n 'domain' => $domain,\n 'name' => $crmData['Name'] ? mb_strimwidth($crmData['Name'], 0, 191) : '',\n 'title' => $crmData['Title'] ? mb_strimwidth($crmData['Title'], 0, 128) : null,\n 'email' => $crmData['Email'] ? mb_strimwidth($crmData['Email'], 0, 80) : null,\n 'phone' => $parsedNumber['phone'],\n 'ext' => $parsedNumber['ext'] ?? null,\n 'mobile_phone' => $mobilePhone,\n 'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n crmConfiguration: $this->config,\n crmProviderId: $crmData['Id'],\n modelType: Lead::class,\n fileName: $crmData['Id'],\n avatarText: $crmData['Name']\n ),\n 'stage_id' => $stage->id,\n 'record_type_id' => null,\n 'converted_at' => $convertedDate,\n 'converted_account_id' => $convertedAccount->id ?? null,\n 'converted_opportunity_id' => $convertedOpportunity->id ?? null,\n 'converted_contact_id' => $convertedContact->id ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $createdDate,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->leads(), $crmData['Id']);\n\n /** @var Lead */\n $lead = $this->config->leads()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n if ($lead->wasChanged('converted_at') && $lead->getConvertedAt() !== null) {\n event(new LeadConverted($lead));\n }\n\n $this->handleObjectDeletion($lead, $crmData);\n\n return $lead;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n $fields = $this->getAllFieldsAsArray('account');\n\n if (\\in_array('Id', $fields, true) === false) {\n return $syncCount;\n }\n\n $query = '\n SELECT ' . rtrim(implode(',', $fields), ',') . '\n FROM Account\n WHERE LastModifiedDate > :since\n ORDER BY LastModifiedDate ASC';\n\n try {\n $sfAccounts = $this->queryHandler->query($query, [\n 'since' => $since->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($sfAccounts as $sfAccount) {\n // Only sync if previously imported.\n if ($this->hasAccount($sfAccount['Id'])) {\n $this->importAccount($sfAccount);\n $syncCount++;\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::ACCOUNT);\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n $fields = $this->getAllFieldsAsArray('account');\n if (! in_array('Id', $fields, true)) {\n $this->logger->info('[Salesforce] Sync account cancelled. Fields are not available.', [\n 'crmId' => $crmId,\n 'userId' => $this->profile->getUserId(),\n ]);\n\n return null;\n }\n\n $sfAccount = $this->getRecord('Account', $crmId, $fields);\n\n return $this->importAccount($sfAccount);\n }\n\n private function importAccount($crmData): Account\n {\n $countryCode = $crmData['BillingCountryCode'] ?? $crmData['ShippingCountryCode'] ?? null;\n\n // Salesforce allows custom \"countries\" to be created. Disregard these.\n if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {\n $countryCode = null;\n }\n\n // If we have no country code, try to parse it from the country names.\n if ($countryCode === null && empty($crmData['BillingCountry']) === false) {\n $countryCode = $this->convertCountryNameToCode($crmData['BillingCountry']);\n }\n\n if ($countryCode === null && empty($crmData['ShippingCountry']) === false) {\n $countryCode = $this->convertCountryNameToCode($crmData['ShippingCountry']);\n }\n\n if (empty($crmData['Phone']) === false) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($crmData['Phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $industry = null;\n if (empty($crmData['Industry']) === false) {\n $industry = mb_strimwidth($crmData['Industry'], 0, 40);\n }\n\n $domain = null;\n if (empty($crmData['Website']) === false) {\n $domain = mb_strimwidth($crmData['Website'], 0, 191);\n $domain = StringUtil::resolveDomain($domain);\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $data = [\n 'team_id' => $this->team->id,\n 'user_id' => $profile?->user_id,\n 'owner_id' => $crmData['OwnerId'],\n 'name' => mb_strimwidth($crmData['Name'], 0, 191),\n 'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n crmConfiguration: $this->config,\n crmProviderId: $crmData['Id'],\n modelType: Account::class,\n fileName: $crmData['Id'],\n avatarText: $crmData['Name']\n ),\n 'industry' => $industry,\n 'domain' => $domain,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $createdDate,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->accounts(), $crmData['Id']);\n\n /** @var Account */\n $account = $this->config->accounts()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n $this->handleObjectDeletion($account, $crmData);\n\n return $account;\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n\n $syncCount = 0;\n $logParams = $parameters;\n $parameters['profile'] = $this->profile;\n $logParams['user'] = $this->profile->getUserId();\n\n if (count($strategies) > 1) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Multiple sync strategies used', [\n 'teamId' => $this->team->getUuid(),\n 'params' => $logParams,\n 'strategies_count' => count($strategies),\n ]);\n }\n\n foreach ($strategies as $syncStrategy) {\n $name = $syncStrategy->getStrategyName();\n\n try {\n $sfOpportunities = $syncStrategy->fetchOpportunities($parameters);\n $totalRecords = $sfOpportunities->count();\n\n foreach ($sfOpportunities as $sfOpportunity) {\n $this->importOpportunity($sfOpportunity);\n $syncCount++;\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n $this->logger->warning('[' . $this->getDisplayName() . '] No opportunities found', [\n 'teamId' => $this->team->getUuid(),\n 'name' => $name,\n 'params' => $logParams,\n 'reason' => $noResultsException->getMessage(),\n ]);\n } catch (CrmException $crmException) {\n // Nothing to sync.\n $this->logger->warning('[' . $this->getDisplayName() . '] Opportunity sync failed', [\n 'teamId' => $this->team->getUuid(),\n 'name' => $name,\n 'params' => $logParams,\n 'reason' => $crmException->getMessage(),\n ]);\n }\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::OPPORTUNITY, ['params' => $logParams]);\n\n // debug to see how if count of opportunities reaches 1000\n if ($syncCount >= 1000) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Sync Opportunities - count warning',\n [\n 'team_id' => $this->team->getId(),\n 'params' => $logParams,\n 'count' => $syncCount,\n 'strategies_count' => count($strategies),\n 'total_records' => $totalRecords ?? null,\n ]\n );\n }\n\n return $syncCount;\n }\n\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY\n );\n\n $parameters = [\n 'profile' => $this->profile,\n 'crm_id' => $crmId,\n ];\n\n try {\n $sfOpportunity = $strategy->fetchOpportunities($parameters);\n } catch (HttpNotFoundException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->id_string,\n 'crmId' => $crmId,\n ]);\n\n return null;\n } catch (CrmException $crmException) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity sync failed', [\n 'teamId' => $this->team->id_string,\n 'crmId' => $crmId,\n 'exception' => $crmException->getMessage(),\n ]);\n\n return null;\n }\n\n if ($sfOpportunity instanceof ArrayIterator) {\n return $this->importOpportunity($sfOpportunity->getItems());\n }\n\n return $this->importOpportunity($sfOpportunity);\n }\n\n private function importOpportunity($crmData): ?Opportunity\n {\n /** @var ?Stage $stage */\n $stage = null;\n if (isset($crmData['StageName'])) {\n $stage = $this->config\n ->stages()\n ->where('name', $crmData['StageName'])\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->orderBy('is_selectable', 'DESC')\n ->orderBy('id')\n ->first();\n\n if ($stage === null) {\n // Import it.\n $stage = $this->importStages([Stage::TYPE_OPPORTUNITY], $crmData['StageName']);\n }\n }\n\n $recordType = null;\n if (empty($crmData['RecordTypeId']) === false) {\n /** @var ?RecordType $recordType */\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $crmData['RecordTypeId'])\n ->first();\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $account = null;\n if (empty($crmData['AccountId']) === false) {\n /** @var ?Account $account */\n $account = $this->config->accounts()\n ->where('crm_provider_id', (string) $crmData['AccountId'])\n ->first();\n\n if ($account === null) {\n $account = $this->syncAccount($crmData['AccountId']);\n }\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $closeDate = null;\n if (empty($crmData['CloseDate']) === false) {\n $closeDate = Carbon::parse($crmData['CloseDate'])->format('Y-m-d');\n }\n\n $valueFieldName = 'Amount';\n if ($this->config->opportunity_value_field_id) {\n $valueFieldName = $this->config->opportunityValueField->crm_provider_id;\n }\n\n $data = [\n 'team_id' => $this->team->id,\n 'account_id' => $account->id ?? null,\n 'user_id' => $profile?->user_id ?? null,\n 'owner_id' => $crmData['OwnerId'] ?? null,\n 'name' => mb_strimwidth($crmData['Name'] ?? '', 0, 128),\n 'value' => $crmData[$valueFieldName],\n 'currency_code' => CurrencyFormatter::formatCode($crmData['CurrencyIsoCode'] ?? null),\n 'close_date' => $closeDate,\n 'is_closed' => $crmData['IsClosed'],\n 'is_won' => $crmData['IsWon'],\n 'stage_id' => $stage?->id ?? null,\n 'record_type_id' => $recordType->id ?? null,\n 'remotely_created_at' => $createdDate,\n 'probability' => $crmData['Probability'] ?? null,\n 'forecast_category' => $crmData['ForecastCategoryName'] ?? null,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->opportunities(), $crmData['Id']);\n\n // Do not allow locked DB tables & other errors\n // to interrupt the process of reverting the trashed opportunities\n try {\n /** @var Opportunity $opportunity */\n $opportunity = $this->config->opportunities()\n ->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n // import external fields into crm_field_data if present\n $crmFields = $this->getOpportunitySyncableFields();\n\n $this->importOpportunityCrmFieldData($crmData, $crmFields, $opportunity->id);\n\n $this->handleObjectDeletion($opportunity, $crmData);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n } catch (Exception $exception) {\n Sentry::captureException($exception);\n\n $this->logger->error('[Salesforce] importOpportunity failure.', [\n 'crm_provider_id' => $crmData['Id'],\n 'team_id' => $this->team->id,\n 'exception' => $exception->getMessage(),\n ]);\n\n $this->handleEntityDeletionByProviderId($this->config->opportunities(), $crmData);\n }\n\n return null;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n $fields = $this->getAllFieldsAsArray('contact');\n if (\\in_array('Id', $fields, true) === false) {\n return $syncCount;\n }\n\n $query = '\n SELECT ' . rtrim(implode(',', $fields), ',') . '\n FROM Contact\n WHERE LastModifiedDate > :since\n ORDER BY LastModifiedDate ASC';\n\n try {\n $sfContacts = $this->queryHandler->query($query, [\n 'since' => $since->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($sfContacts as $sfContact) {\n // Only sync if previously imported.\n if ($this->hasContact($sfContact['Id'])) {\n $this->importContact($sfContact);\n $syncCount++;\n }\n }\n } catch (NoResultsException $noResultsException) {\n // Nothing to sync.\n }\n\n $this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::CONTACT);\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n $fields = $this->getAllFieldsAsArray('contact');\n if (! in_array('Id', $fields, true)) {\n $this->logger->info('[Salesforce] Sync contact cancelled. Fields are not available.', [\n 'crmId' => $crmId,\n 'userId' => $this->profile->getUserId(),\n ]);\n\n return null;\n }\n\n $sfContact = $this->getRecord('Contact', $crmId, $fields);\n\n return $this->importContact($sfContact);\n }\n\n private function importContact($crmData): Contact\n {\n $account = null;\n // Contacts may not have accounts...\n if (isset($crmData['AccountId'])) {\n $account = $this->config->accounts()\n ->where('crm_provider_id', (string) $crmData['AccountId'])\n ->first();\n\n if ($account === null) {\n $account = $this->syncAccount($crmData['AccountId']);\n }\n }\n\n $countryCode = $crmData['MailingCountryCode'] ?? null;\n\n // Salesforce allows custom \"countries\" to be created. Disregard these.\n if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {\n $countryCode = null;\n }\n\n // If we have no country code, try to parse it from the country name.\n if ($countryCode === null && empty($crmData['MailingCountry']) === false) {\n $countryCode = $this->convertCountryNameToCode($crmData['MailingCountry']);\n\n if ($countryCode === null && $account) {\n $countryCode = $account->country_code;\n }\n }\n\n $ext = null;\n $parsedNumber = [];\n if (empty($crmData['Phone']) === false) {\n $number = Str::limit($crmData['Phone'], 25, '');\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n\n if (empty($parsedNumber['ext']) === false) {\n $ext = Str::limit($parsedNumber['ext'], 10, '');\n }\n }\n\n $mobileNumber = null;\n if (empty($crmData['MobilePhone']) === false) {\n $mobileNumber = Str::limit(phone_e164($countryCode, $crmData['MobilePhone']), 25, '');\n }\n\n $createdDate = null;\n if (empty($crmData['CreatedDate']) === false) {\n $createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');\n }\n\n $profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);\n\n $data = [\n 'team_id' => $this->team->id,\n 'account_id' => $account->id ?? null,\n 'user_id' => $profile?->user_id,\n 'owner_id' => $crmData['OwnerId'] ?? null,\n 'name' => ($crmData['Name'] ?? null) !== null ? mb_strimwidth($crmData['Name'], 0, 100) : '',\n 'title' => ($crmData['Title'] ?? null) !== null ? mb_strimwidth($crmData['Title'], 0, 128) : null,\n 'email' => ($crmData['Email'] ?? null) !== null ? mb_strimwidth($crmData['Email'], 0, 191) : null,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $ext,\n 'mobile_phone' => $mobileNumber,\n 'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n crmConfiguration: $this->config,\n crmProviderId: $crmData['Id'],\n modelType: Contact::class,\n fileName: $crmData['Id'],\n avatarText: $crmData['Name']\n ),\n 'remotely_created_at' => $createdDate,\n ];\n\n $this->restoreAnyTrashedEntity($this->config->contacts(), $crmData['Id']);\n\n /** @var Contact */\n $contact = $this->config->contacts()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);\n\n $this->handleObjectDeletion($contact, $crmData);\n\n return $contact;\n }\n\n /**\n * @inheritdoc\n */\n public function syncOrganization(): void\n {\n $fields = [\n 'InstanceName',\n 'OrganizationType',\n 'IsSandbox',\n ];\n\n $orgValues = $this->getRecord('Organization', $this->config->crm_provider_id, $fields);\n\n $edition = null;\n switch ($orgValues['OrganizationType']) {\n case 'Developer Edition':\n $edition = Configuration::EDITION_DEVELOPER;\n\n break;\n\n case 'Professional Edition':\n $edition = Configuration::EDITION_PROFESSIONAL;\n\n break;\n\n case 'Enterprise Edition':\n $edition = Configuration::EDITION_ENTERPRISE;\n\n break;\n }\n\n $this->config->edition = $edition;\n $this->config->instance = $orgValues['InstanceName'];\n\n // XXX: How can this state be possible?\n if ($this->config->version === null) {\n $this->config->version = Client::MIN_API_VERSION;\n }\n\n $installedVersion = $this->getInstalledAppVersion();\n if ($installedVersion !== null) {\n $installedVersion = (string) $this->getInstalledAppVersion();\n }\n\n $this->config->installed_app_version = $installedVersion;\n\n $this->config->save();\n }\n\n public function getInstalledAppVersion(): ?string\n {\n try {\n $query = '\n SELECT\n SubscriberPackageVersion.MajorVersion,\n SubscriberPackageVersion.MinorVersion,\n SubscriberPackageVersion.PatchVersion,\n SubscriberPackageVersion.BuildNumber\n FROM\n InstalledSubscriberPackage\n WHERE\n SubscriberPackageId = :packageId\n ';\n\n $sfFields = $this->queryHandler->metadata($query, [\n 'packageId' => self::INSTALLED_PACKAGE_ID,\n ]);\n\n // There is always 1 result at this point.\n $sfField = $sfFields->current();\n\n // Grab version number.\n $version = $sfField['SubscriberPackageVersion']['MajorVersion'] .\n $sfField['SubscriberPackageVersion']['MinorVersion'] .\n $sfField['SubscriberPackageVersion']['PatchVersion'] .\n $sfField['SubscriberPackageVersion']['BuildNumber'];\n } catch (\\Exception) {\n $version = null;\n }\n\n return $version;\n }\n\n public function saveActivity(Activity $activity): Activity\n {\n $playbook = $this->getPlaybookFromActivity($activity);\n\n if ($playbook === null) {\n throw new \\InvalidArgumentException('Please configure a Playbook first.');\n }\n\n $payload = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'Description' => (new DecorateActivity())->generateDescription($activity),\n ];\n\n // If the activity name matches a know activity type, set that here.\n if ($this->matchesCrmType($activity->category)) {\n $payload += [\n $playbook->activityField->crm_provider_id => $activity->category->name,\n 'Subject' => (new DecorateActivity())->generateTitle($activity),\n ];\n } else {\n $subject = $activity->category->name;\n if ($activity->getTitle() !== null) {\n $subject .= ': ' . $activity->getTitle();\n }\n\n $payload += [\n 'Subject' => $subject,\n ];\n }\n\n if ($activity->account_id) {\n $hasLinkFeature = $activity->user->team->hasFeature(\n FeatureEnum::LINK_ACTIVITY_TO_MULTIPLE_PROSPECTS\n );\n if (($hasLinkFeature && $activity->opportunity_id && ! $activity->contact_id)\n || (! $hasLinkFeature && $activity->hasOpportunity())\n ) {\n $payload += ['WhatId' => $activity->opportunity->crm_provider_id];\n } else {\n $payload += ['WhatId' => $activity->account->crm_provider_id];\n }\n }\n\n if ($activity->contact_id) {\n $payload += ['WhoId' => $activity->contact->crm_provider_id];\n } elseif ($activity->lead_id) {\n // Sync the lead to get fresh data instead of the cached one.\n $leadData = $this->syncLead($activity->lead->crm_provider_id);\n\n // Checking whenever we log if the lead is actually converted.\n if ($leadData['converted_at'] !== null) {\n $convertedLead = $this->config->leads()->find($activity->lead_id);\n $convertedOpportunity = null;\n $activity->lead_id = null;\n\n if ($convertedLead) {\n if ($convertedLead->account) {\n // Overwrite account's crm_provider_id with converted account one.\n $payload += ['WhatId' => $convertedLead->account->crm_provider_id];\n\n $activity->account_id = $convertedLead->account->id;\n }\n\n if ($convertedLead->opportunity) {\n // Overwrite opportunity crm_provider_id with converted opportunity one.\n $convertedOpportunity = $convertedLead->opportunity;\n $payload += ['WhatId' => $convertedOpportunity->crm_provider_id];\n\n $activity->opportunity_id = $convertedOpportunity->id;\n $activity->value = $convertedOpportunity->value;\n }\n\n if ($convertedLead->contact) {\n // Overwrite contact crm_provider_id with converted contact one.\n $payload += ['WhoId' => $convertedLead->contact->crm_provider_id];\n\n $activity->contact_id = $convertedLead->contact->id;\n }\n }\n\n // If there is converted opportunity, use it to update the stage, otherwise update it to null,\n // because you can’t have an account/contact on an activity and a lead stage.\n $activity->stage_id = $convertedOpportunity ? $convertedOpportunity['stage_id'] : null;\n\n // Update activity with correct data, pointing to the correct account/contact/opportunity and stage.\n $activity->save();\n\n event(new ActivityLeadConverted($activity, $leadData));\n } else {\n $payload += [\n 'WhoId' => $activity->lead->crm_provider_id,\n 'WhatId' => null, // In case it was set on the remote record.\n ];\n }\n }\n\n if ($playbook->activity_type === Playbook::ACTIVITY_TYPE_TASK) {\n // Generate payload.\n $payload = array_merge($payload, $this->buildTaskPayload($activity));\n\n // Check if the activity should be logged under an existing task or created fresh.\n if ($activity->hasCrmProviderId()) {\n $this->updateCrmActivity(Field::OBJECT_TASK, $activity, $payload);\n } else {\n $activityId = $this->createRecord('Task', $payload);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n }\n } else {\n // Generate payload.\n $payload = array_merge($payload, $this->buildEventPayload($activity));\n\n // Check if the activity should be logged under an existing event or created fresh.\n if ($activity->hasCrmProviderId()) {\n $this->updateCrmActivity(Field::OBJECT_EVENT, $activity, $payload);\n } else {\n $activityId = $this->createRecord('Event', $payload);\n\n $activity->crm_provider_id = $activityId;\n $activity->save();\n }\n }\n\n return $activity;\n }\n\n private function updateCrmActivity(string $objectType, Activity $activity, array $payload): void\n {\n $sfActivity = $this->getRecord(\n objectType: $objectType,\n objectId: $activity->getCrmProviderId(),\n fields: ['Description', 'WhoId', 'WhatId']\n );\n\n if (! empty($payload['WhoId']) && $sfActivity['WhoId'] !== $payload['WhoId']) {\n $this->logger->info('[Salesforce] Updating WhoId', [\n 'objectType' => $objectType,\n 'activityId' => $activity->getUuid(),\n ]);\n\n if (! empty($payload['WhatId']) && $sfActivity['WhatId'] !== $payload['WhatId']) {\n $this->logger->info('[Salesforce] Updating WhatId', [\n 'objectType' => $objectType,\n 'activityId' => $activity->getUuid(),\n ]);\n } else {\n $payload['WhatId'] = null;\n }\n }\n\n $decorateActivity = new DecorateActivity();\n $payload['Description'] = $decorateActivity->mergeDescriptions(\n $payload['Description'],\n $sfActivity['Description'] ?? null\n );\n\n $this->logger->info('[Salesforce] Updating CRM activity data', [\n 'objectType' => $objectType,\n 'activityId' => $activity->getUuid(),\n 'crmActivity' => $sfActivity,\n 'payload' => $payload,\n ]);\n\n $this->updateRecord(\n objectType: $objectType,\n objectId: $activity->getCrmProviderId(),\n data: $payload\n );\n }\n\n /**\n * Store transcripts as note.\n *\n * @throws \\Exception\n */\n public function createTranscriptNotes(Activity $activity): void\n {\n // For SF we also check if Log Notes is enabled.\n if ($this->profile->log_notes === Profile::LOG_NOTE_NONE) {\n return;\n }\n\n if ($activity->opportunity_id && $activity->prospect === null) {\n return;\n }\n\n try {\n $transcriptionData = $this->generateTranscription($activity);\n\n $noteMaxLength = $this->profile->log_notes === Profile::LOG_NOTE_ENHANCED\n ? self::ENHANCED_NOTE_MAX_LENGTH\n : self::CLASSIC_NOTE_MAX_LENGTH;\n\n $title = 'Transcript for ';\n $title .= $activity->title ?? $activity->activity_title;\n\n // Truncate Notes with max notes length because transcription text could be very long.\n $body = mb_strimwidth($transcriptionData, 0, $noteMaxLength);\n\n if ($activity->opportunity_id) {\n $objectId = $activity->opportunity->crm_provider_id;\n } else {\n $objectId = $activity->prospect->crm_provider_id;\n }\n\n $noteId = $this->saveNote($title, $body, $objectId);\n\n // Store crm logged id in transcription.\n $transcription = $activity->getTranscription();\n $transcription->crm_activity_id = $noteId;\n $transcription->save();\n } catch (\\Exception $e) {\n \\Sentry::captureException($e);\n }\n }\n\n private function buildTaskPayload(Activity $activity): array\n {\n $payload = [\n 'Status' => 'Completed',\n ];\n\n switch ($activity->getCrmType()) {\n case Activity::TYPE_SOFTPHONE:\n case Activity::TYPE_SOFTPHONE_INBOUND:\n case Activity::TYPE_CONFERENCE:\n\n // \"Due Date\" is stored as UTC and should reflect the users local time preference.\n $activityDate = $activity->actual_start_time\n ? $activity->actual_start_time->tz($activity->user->timezone)->toDateString()\n : $activity->created_at->tz($activity->user->timezone)->toDateString();\n\n if ($activity->is_internal) {\n $callType = 'Internal';\n } elseif ($activity->isTypeSoftPhone() && $activity->getProvider() !== Activity::PROVIDER_UPLOADER) {\n $callType = 'Outbound';\n } else {\n $callType = 'Inbound';\n }\n\n $payload += [\n 'CallDurationInSeconds' => $activity->duration,\n 'CallType' => $callType,\n 'CallObject' => $activity->getUuid(),\n 'ActivityDate' => $activityDate,\n ];\n\n if ($activity->crm_provider_id === null) {\n // Fields that can only be set on initial creation.\n $payload += [\n 'TaskSubtype' => 'Call',\n ];\n }\n\n break;\n\n case Activity::TYPE_SMS_OUTBOUND:\n case Activity::TYPE_SMS_INBOUND:\n default:\n $payload += [\n 'ActivityDate' => $activity->created_at->tz($activity->user->timezone)->toDateString(),\n ];\n\n break;\n }\n\n $payload = $this->payloadBuilder\n ->addCustomLogicFieldsPayload($activity, $payload, Field::OBJECT_TASK);\n\n return array_merge($payload, $this->fetchCustomFieldData($activity, Field::OBJECT_TASK));\n }\n\n private function buildEventPayload(Activity $activity): array\n {\n $startDateTime = $activity->scheduled_start_time;\n if ($activity->actual_start_time) {\n $startDateTime = $activity->actual_start_time;\n }\n\n $endDateTime = $activity->scheduled_end_time;\n if ($activity->actual_end_time) {\n $endDateTime = $activity->actual_end_time;\n }\n\n // If the call never closed (e.g. cancelled) just set EndDateTime to StartDateTime.\n if ($endDateTime === null) {\n $endDateTime = $startDateTime;\n }\n\n $payload = [\n 'StartDateTime' => $startDateTime->format('Y-m-d\\TH:i:s\\Z'),\n 'EndDateTime' => $endDateTime->format('Y-m-d\\TH:i:s\\Z'),\n ];\n\n $payload = $this->payloadBuilder->addCustomLogicFieldsPayload($activity, $payload, Field::OBJECT_EVENT);\n\n return array_merge($payload, $this->fetchCustomFieldData($activity, Field::OBJECT_EVENT));\n }\n\n private function fetchCustomFieldData(Activity $activity, string $objectType): array\n {\n $payload = [];\n\n $fieldDataRepository = app(FieldDataRepository::class);\n $fieldData = $fieldDataRepository->getActivityFieldData($activity, $objectType);\n\n foreach ($fieldData as $data) {\n // Check the field is custom and add it to the payload.\n if ($this->isCustomField($data->getField())) {\n // Add the field and value to the payload.\n $payload += [\n $data->getField()->getCrmProviderId() => $data->getValue(),\n ];\n }\n }\n\n return $payload;\n }\n\n public function saveFollowupActivity(Activity $fromActivity, array $fields): ?string\n {\n $playbook = $this->getPlaybook($fromActivity->getUser());\n\n if ($playbook === null) {\n throw new \\InvalidArgumentException('Please configure a Playbook first.');\n }\n\n $activityType = $playbook->getActivityType();\n // This is the user provided activity type field.\n // Todo: don't require subject and instead check if a date/startdate is set.\n if (empty($fields['Subject']) && empty($fields['Type'])) {\n return null;\n }\n\n $activityTypeField = $playbook->activityField->crm_provider_id;\n\n $payload = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'Subject' => $fields['Subject'] ?? $fields['Type'] . ' with ' . $fromActivity->prospect_name,\n $activityTypeField => $fields['Type'] ?? null,\n ];\n\n if ($fromActivity->account) {\n if ($fromActivity->opportunity) {\n $payload += ['WhatId' => $fromActivity->opportunity->crm_provider_id];\n } else {\n $payload += ['WhatId' => $fromActivity->account->crm_provider_id];\n }\n }\n\n if ($fromActivity->contact) {\n $payload += ['WhoId' => $fromActivity->contact->crm_provider_id];\n } elseif ($fromActivity->lead) {\n $payload += ['WhoId' => $fromActivity->lead->crm_provider_id];\n }\n\n if ($activityType === Playbook::ACTIVITY_TYPE_TASK) {\n // Generate payload.\n $payload = array_merge($payload, $this->buildFollowupTaskPayload($fields));\n\n $activityId = $this->createRecord('Task', $payload);\n } else {\n // Generate payload.\n $payload = array_merge($payload, $this->buildFollowupEventPayload($fields));\n\n $activityId = $this->createRecord('Event', $payload);\n }\n\n // We don't actually create a corresponding activity object on our side yet.\n return $activityId;\n }\n\n private function buildFollowupTaskPayload(array $fields): array\n {\n $payload = [\n 'ActivityDate' => $fields['ActivityDate'] ?? date('Y-m-d'),\n 'TaskSubtype' => 'Call',\n ];\n\n if (empty($fields['Priority']) === false) {\n $payload += [\n 'Priority' => $fields['Priority'],\n ];\n }\n\n if (empty($fields['Description']) === false) {\n $payload += [\n 'Description' => $fields['Description'],\n ];\n }\n\n if (empty($fields['Status']) === false) {\n $payload += [\n 'Status' => $fields['Status'],\n ];\n }\n\n if (empty($fields['ReminderDateTime']) === false) {\n $payload += [\n 'ReminderDateTime' => $fields['ReminderDateTime'],\n 'IsReminderSet' => true,\n ];\n }\n\n return $payload;\n }\n\n private function buildFollowupEventPayload(array $fields): array\n {\n $payload = [\n 'StartDateTime' => $fields['StartDateTime'] ?? date('Y-m-d\\TH:i:s\\Z'),\n ];\n\n if (empty($fields['EndDateTime'])) {\n $payload += [\n 'IsAllDayEvent' => true,\n ];\n } else {\n $payload += [\n 'EndDateTime' => $fields['EndDateTime'],\n ];\n }\n\n if (empty($fields['Description']) === false) {\n $payload += [\n 'Description' => $fields['Description'],\n ];\n }\n\n if (empty($fields['Status']) === false) {\n $payload += [\n 'Status' => $fields['Status'],\n ];\n }\n\n if (empty($fields['ReminderDateTime']) === false) {\n $payload += [\n 'ReminderDateTime' => $fields['ReminderDateTime'],\n 'IsReminderSet' => true,\n ];\n }\n\n return $payload;\n }\n\n public function saveNote(string $title, string $body, string $objectId, ?NoteObject $noteObject = null): ?string\n {\n $noteId = null;\n\n try {\n if ($this->profile->log_notes === Profile::LOG_NOTE_ENHANCED) {\n $noteId = $this->buildEnhancedNote($title, $body, $objectId);\n } else {\n $noteId = $this->buildClassicNote($title, $body, $objectId);\n }\n } catch (HttpNotFoundException $exception) {\n // The profile not having access to create Enhanced Notes. Set their preference to Classic.\n if ($this->profile->log_notes === Profile::LOG_NOTE_ENHANCED) {\n $this->profile->update([\n 'log_notes' => Profile::LOG_NOTE_CLASSIC,\n ]);\n }\n }\n\n return $noteId;\n }\n\n /**\n * This is using the \"Enhanced\" Notes feature, NOT the \"Notes & Attachments\" feature being deprecated.\n *\n * @url https://salesforce.stackexchange.com/questions/104408/how-can-i-create-an-account-note-or-contact-note-via-api-that-is-visible-in-sale\n */\n private function buildEnhancedNote(string $title, string $body, string $objectId): string\n {\n // Decode stored entities, escape HTML (without quoting), then convert line breaks for Salesforce formatting\n $decodedBody = html_entity_decode($body, ENT_QUOTES | ENT_HTML5);\n $sanitizedBody = htmlspecialchars($decodedBody, ENT_NOQUOTES, 'UTF-8', false);\n $content = nl2br($sanitizedBody, false);\n $note = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'Title' => $title,\n 'Content' => base64_encode($content),\n ];\n\n $noteId = $this->createRecord('ContentNote', $note);\n\n $link = [\n 'ContentDocumentId' => $noteId,\n 'LinkedEntityId' => $objectId,\n 'ShareType' => 'I',\n ];\n\n $this->createRecord('ContentDocumentLink', $link);\n\n return $noteId;\n }\n\n private function buildClassicNote(string $title, string $body, string $objectId): string\n {\n if (in_array($this->parseObjectType($objectId), [Field::OBJECT_TASK, Field::OBJECT_EVENT])) {\n $this->logger->info('[Salesforce] Summary not sent', [\n 'profile_id' => $this->profile->id,\n 'objectId' => $objectId,\n 'reason' => 'Classical Note does not support Task/Event relation',\n ]);\n\n return '';\n }\n\n $titleTrimmed = null;\n\n if (mb_strlen($title) > 80) {\n $titleTrimmed = substr($title, 0, 77) . '...';\n }\n $payload = [\n 'OwnerId' => $this->profile->crm_provider_id,\n 'IsPrivate' => false,\n 'Title' => $titleTrimmed ?? $title,\n 'Body' => $titleTrimmed ? $title . PHP_EOL . $body : $body,\n 'ParentId' => $objectId,\n ];\n\n return $this->createRecord('Note', $payload);\n }\n\n /**\n * @inheritdoc\n */\n public function find(string $name, array $scopes): array\n {\n if ($this->profile === null) {\n return [];\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $limitValues = ['limit' => $this->limit, 'offset' => $this->offset];\n $sosl = $queryBuilder->buildFindQuery($name, $scopes, $limitValues);\n\n $this->logger->info('[Salesforce] Find prospects', [\n 'profile_id' => $this->profile->id,\n 'sosl_query' => $sosl,\n 'search_string' => $name,\n 'scopes' => $scopes,\n ]);\n\n $data = Cache::remember($this->profile->id . $sosl, self::CACHE_TTL, function () use ($sosl) {\n $data = [];\n\n try {\n // Hit remote API.\n $objects = $this->queryHandler->search($sosl);\n\n // Build mapped list.\n foreach ($objects as $object) {\n $type = strtolower($object['attributes']['type']);\n\n $record = [\n 'crmId' => $object['Id'],\n 'name' => $object['Name'],\n 'prospectType' => $type,\n 'phoneNumbers' => [],\n 'crmUrl' => $this->generateProviderUrl($object['Id'], $type),\n ];\n\n switch ($type) {\n case 'lead':\n if (empty($object['Company']) === false) {\n $record['organization'] = $object['Company'];\n }\n\n if (empty($object['Title']) === false) {\n $record['title'] = $object['Title'];\n }\n\n $stage = $this->config->stages()\n ->where('type', Stage::TYPE_LEAD)\n ->where('name', $object['Status'])\n ->first();\n\n // Lazy create the stage.\n if ($stage === null) {\n $stage = $this->importStages([Stage::TYPE_LEAD], $object['Status']);\n }\n\n if ($stage) {\n $record += [\n 'stage' => [\n 'id' => $stage->id_string,\n 'name' => $stage->name,\n ],\n ];\n }\n\n if (empty($object['RecordTypeId']) === false) {\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $object['RecordTypeId'])\n ->first();\n\n if ($recordType) {\n $record += [\n 'recordType' => [\n 'id' => $recordType->id_string,\n 'name' => $recordType->name,\n ],\n ];\n }\n }\n\n break;\n\n case 'account':\n if (empty($object['Industry']) === false) {\n $record['industry'] = $object['Industry'];\n $record['detailsLine'] = $object['Industry'];\n }\n if (! empty($object['PersonEmail'])) {\n $record['detailsLine'] = $object['PersonEmail'];\n }\n\n break;\n\n case 'contact':\n // For contacts, we should try and fetch their account name too.\n if ($object['AccountId']) {\n // Cheaper to get this locally.\n $account = $this->config->accounts()\n ->where('crm_provider_id', $object['AccountId'])\n ->first(['name']);\n\n if ($account) {\n $record['organization'] = $account->name;\n }\n }\n\n if (! empty($object['IsPersonAccount']) && $object['Email']) {\n $record['detailsLine'] = $object['Email'];\n } else {\n if (empty($object['Title']) === false) {\n $record['title'] = $object['Title'];\n }\n }\n\n break;\n }\n\n // Add phone numbers to record.\n if (empty($object['Phone']) === false && $object['Phone']) {\n $record['phoneNumbers'][] = [\n 'number' => $object['Phone'],\n 'nationalFormat' => phone_national($this->profile->user->country_code, $object['Phone']),\n 'type' => 'phone',\n ];\n }\n\n if (empty($object['MobilePhone']) === false && $object['MobilePhone']) {\n $record['phoneNumbers'][] = [\n 'number' => $object['MobilePhone'],\n 'nationalFormat' => phone_national(\n $this->profile->user->country_code,\n $object['MobilePhone']\n ),\n 'type' => 'mobile',\n ];\n }\n\n $data[] = $record;\n }\n } catch (NoResultsException $e) {\n $data = [];\n }\n\n return $data;\n });\n\n return $data;\n }\n\n /**\n * @inheritdoc\n */\n public function findOpportunities(?string $crmAccountId, ?string $crmContactId, ?int $userId = null): array\n {\n $data = [];\n $ownerData = [];\n $ownerId = null;\n\n if ($crmAccountId === null) {\n return $data;\n }\n\n if ($userId) {\n $profileRepository = app(ProfileRepository::class);\n $profile = $profileRepository->findProfileByUserId($this->config, $userId);\n\n $ownerId = $profile instanceof Profile ? $profile->getCrmProviderId() : null;\n }\n\n try {\n // Perhaps their profile has no opportunity permissions.\n if ($this->profile === null || $this->profile->opportunity_fields === null) {\n return $data;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $query = $queryBuilder->buildFindOpportunitiesQuery();\n\n $objects = $this->queryHandler->query($query, ['accountId' => $crmAccountId]);\n\n foreach ($objects as $object) {\n $record = [\n 'crmId' => $object['Id'],\n 'name' => $object['Name'],\n 'won' => $object['IsWon'],\n 'closed' => $object['IsClosed'],\n ];\n\n $valueFieldName = 'Amount';\n if ($this->config->opportunity_value_field_id) {\n $valueFieldName = $this->config->opportunityValueField->crm_provider_id;\n }\n\n if (empty($object[$valueFieldName]) === false) {\n $currency = $object['CurrencyIsoCode'] ?? $this->config->default_currency;\n $value = formatCurrency($object[$valueFieldName], $currency);\n\n $record += [\n 'value' => $value,\n ];\n }\n\n $stage = $this->config->stages()\n ->where('type', Stage::TYPE_OPPORTUNITY)\n ->where('name', $object['StageName'])\n ->first();\n\n // Lazy create the stage.\n if ($stage === null) {\n $stage = $this->importStages([Stage::TYPE_OPPORTUNITY], $object['StageName']);\n }\n\n $record += [\n 'stage' => [\n 'id' => $stage->id_string,\n 'name' => $stage->name,\n ],\n ];\n\n if (empty($object['RecordTypeId']) === false) {\n $recordType = $this->config->recordTypes()\n ->where('crm_provider_id', $object['RecordTypeId'])\n ->first();\n\n if ($recordType) {\n $record += [\n 'recordType' => [\n 'id' => $recordType->id_string,\n 'name' => $recordType->name,\n ],\n ];\n }\n }\n\n if ($ownerId && isset($object['OwnerId']) && $object['OwnerId'] === $ownerId) {\n $ownerData[] = $record;\n }\n\n $data[] = $record;\n }\n } catch (NoResultsException $e) {\n return $data;\n }\n\n if (! empty($ownerData)) {\n return $ownerData;\n }\n\n return $data;\n }\n\n public function getContactRolesFromCrm(?Carbon $since = null): array\n {\n $roles = [];\n\n if ($this->profile === null) {\n return $roles;\n }\n\n $queryBuilder = app(QueryBuilder::class, ['profile' => $this->profile]);\n\n $query = $queryBuilder->buildGetContactRolesQuery($since);\n\n try {\n $objects = $this->queryHandler->query($query);\n\n foreach ($objects as $object) {\n $roles[] = [\n 'id' => $object['Id'],\n 'contactId' => $object['ContactId'],\n 'opportunityId' => $object['OpportunityId'],\n 'ownerId' => $object['Opportunity']['OwnerId'] ?? null,\n 'isPrimary' => $object['IsPrimary'],\n 'role' => $object['Role'],\n ];\n }\n } catch (NoResultsException) {\n // Just return an empty array.\n $this->logger->info('[Salesforce] No contact roles found', [\n 'since' => $since?->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n }\n\n return $roles;\n }\n\n public function syncContactRoles(Carbon $since): int\n {\n $contactRoleRepository = app(ContactRoleRepository::class);\n\n $crmContactRoles = $this->getContactRolesFromCrm(since: $since);\n $syncCount = 0;\n $contactRoles = [];\n\n foreach ($crmContactRoles as $crmContactRole) {\n $contactRoles[] = $this->importContactRole($crmContactRole);\n $syncCount++;\n }\n\n $contactRoleRepository->saveContactRoles($contactRoles);\n\n $this->syncRemotelyDeletedContactRoles();\n\n return $syncCount;\n }\n\n private function importContactRole(array $contactRole): array\n {\n $contact = $this->config->contacts()\n ->where('crm_provider_id', $contactRole['contactId'])\n ->first();\n\n if ($contact === null) {\n $contact = $this->syncContact($contactRole['contactId']);\n }\n\n $opportunity = $this->config->opportunities()\n ->where('crm_provider_id', $contactRole['opportunityId'])\n ->first();\n\n if ($opportunity === null) {\n $opportunity = $this->syncOpportunity($contactRole['opportunityId']);\n }\n\n $role = null;\n if (! empty($contactRole['role'])) {\n $role = mb_strimwidth($contactRole['role'], 0, 191);\n }\n\n return [\n 'crm_configuration_id' => $this->config->getId(),\n 'contact_id' => $contact->getId(),\n 'crm_provider_id' => $contactRole['id'],\n 'subject_type' => ContactRole::SUBJECT_TYPE_OPPORTUNITY,\n 'subject_id' => $opportunity->getId(),\n 'is_primary' => $contactRole['isPrimary'],\n 'role' => $role,\n ];\n }\n\n protected function syncRemotelyDeletedContactRoles(): bool\n {\n try {\n $deletedRemotely = $this->queryHandler->queryDeleted('OpportunityContactRole');\n } catch (NoResultsException $e) {\n return false;\n }\n\n $deletedOpportunities = $deletedRemotely->getResults();\n $deletedIds = array_column($deletedOpportunities, 'id');\n\n $contactRoleRepository = app(ContactRoleRepository::class);\n\n foreach (array_chunk($deletedIds, self::HARD_DELETE_CHUNK) as $chunk) {\n $contactRoleRepository->deleteContactRoles($chunk);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Remotely deleted opportunities synced', [\n 'teamId' => $this->team->id_string,\n 'remotelyDeletedOpportunities' => $chunk,\n 'count' => count($chunk),\n ]);\n }\n\n return true;\n }\n\n /**\n * @inheritdoc\n */\n public function getTasks(string $objectType, string $objectId, ?string $opportunityId): array\n {\n $data = $objects = [];\n\n $hasWho = \\in_array($objectType, ['lead', 'contact']);\n $playbook = $this->getPlaybook($this->profile->user);\n $fields = array_merge(\n $this->profile->getFieldsAsArray(parent::OBJECT_TASK),\n $playbook && $playbook->activityField ? [$playbook->activityField->crm_provider_id] : []\n );\n\n // Query should default to any open call for that user.\n $query = '\n SELECT ' . implode(',', array_unique($fields)) . '\n FROM Task\n WHERE OwnerId = :ownerId\n AND IsArchived = false\n AND IsDeleted = false\n AND IsClosed = false\n AND (';\n\n if ($objectType === 'account') {\n // This covers tasks tied to a related contact or opportunity too.\n $query .= '\n AccountId = :accountId';\n }\n\n if ($hasWho) {\n $query .= '\n WhoId = :whoId';\n\n // If we are also going to check on a specific opportunity, set that up.\n if ($opportunityId) {\n $query .= ' OR WhatId = :whatId';\n }\n }\n\n $query .= ' ) ORDER BY LastModifiedDate DESC';\n\n try {\n $objects = $this->queryHandler->query($query, [\n 'ownerId' => $this->profile->crm_provider_id,\n 'whoId' => $objectId,\n 'whatId' => $opportunityId,\n 'accountId' => $objectId,\n ]);\n } catch (NoResultsException $e) {\n return $data;\n } finally {\n $this->logger->debug(sprintf('[Salesforce] Found %s tasks for query \"%s\"', count($objects), $query));\n }\n\n foreach ($objects as $object) {\n $dueDate = $object['ActivityDate'] ? Carbon::parse($object['ActivityDate'])->toIso8601String() : null;\n $data[] = [\n 'crmId' => $object['Id'],\n 'subject' => $object['Subject'],\n 'due' => $dueDate,\n 'type' => $object[$playbook->activityField->crm_provider_id],\n ];\n }\n\n return $data;\n }\n\n /**\n * @inheritdoc\n */\n public function getEvents(string $objectType, string $objectId, ?string $opportunityId): array\n {\n $data = $objects = [];\n $user = $this->profile?->user;\n if ($this->profile === null || $user === null) {\n return $data;\n }\n\n $hasWho = \\in_array($objectType, ['lead', 'contact']);\n $playbook = $this->getPlaybook($user);\n $fields = array_merge(\n $this->profile->getFieldsAsArray(parent::OBJECT_EVENT),\n $playbook && $playbook->activityField ? [$playbook->activityField->crm_provider_id] : []\n );\n\n // Query should default to any event starting in the last week and ending up until today owned by the user.\n $query = '\n SELECT ' . implode(',', array_unique($fields)) . '\n FROM Event\n WHERE OwnerId = :ownerId\n AND IsArchived = false\n AND IsAllDayEvent = false\n AND StartDateTime >= LAST_N_DAYS:7\n AND EndDateTime <= TODAY\n AND (';\n\n if ($objectType === 'account') {\n // This covers events tied to a related contact or opportunity too.\n $query .= '\n AccountId = :accountId';\n }\n\n if ($hasWho) {\n $query .= '\n WhoId = :whoId';\n\n // If we are also going to check on a specific opportunity, set that up.\n if ($opportunityId) {\n $query .= ' OR WhatId = :whatId';\n }\n }\n\n $query .= ' ) ORDER BY LastModifiedDate DESC';\n\n try {\n $objects = $this->queryHandler->query($query, [\n 'ownerId' => $this->profile->crm_provider_id,\n 'whoId' => $objectId,\n 'whatId' => $opportunityId,\n 'accountId' => $objectId,\n ]);\n } catch (NoResultsException $e) {\n return $data;\n } finally {\n $this->logger->debug(sprintf('[Salesforce] Found %s tasks for query \"%s\"', count($objects), $query));\n }\n\n foreach ($objects as $object) {\n $dueDate = $object['StartDateTime'] ? Carbon::parse($object['StartDateTime'])->toIso8601String() : null;\n\n $data[] = [\n 'crmId' => $object['Id'],\n 'subject' => $object['Subject'],\n 'due' => $dueDate,\n 'type' => $object[$playbook->activityField->crm_provider_id],\n ];\n }\n\n return $data;\n }\n\n /**\n * Try to find CRM Objects using email address\n *\n * @return null|array{\n * Lead|null,\n * Account|null,\n * Opportunity|null,\n * Contact|null,\n * Stage|null,\n * string|null\n * }\n */\n public function matchExactlyByEmail(string $email, ?int $userId = null): ?array\n {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $sosl = $queryBuilder->buildMatchByQuery($email, Field::TYPE_EMAIL);\n if ($sosl === null) {\n return null;\n }\n\n try {\n $objects = $this->queryHandler->search($sosl);\n $objects = $this->queryHandler->prioritiseResults(\n $objects,\n $email,\n QueryHandler::PRIORITISE_EMAIL\n );\n\n $data = $this->convertCrmData($objects, $userId);\n\n return ! empty(array_filter($data)) ? $data : null;\n } catch (NoResultsException $e) {\n // Try the account next.\n if ($this->profile->account_fields === null) {\n return null;\n }\n }\n\n return null;\n }\n\n public function getDomain(string $email): ?string\n {\n // SF improved search - strip the domain extension, min domain name length 4\n return $this->getCompanyNameFromEmail(email: $email, minNameLength: 4);\n }\n\n /**\n * Try to find CRM objects using domain name of the email address\n *\n * @return null|array{\n * Lead|null,\n * Account|null,\n * Opportunity|null,\n * Contact|null,\n * Stage|null,\n * string|null\n * }\n */\n public function matchByDomain(string $domain, ?int $userId = null): ?array\n {\n $companyName = $domain;\n\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $sosl = $queryBuilder->buildMatchByDomainQuery($companyName);\n\n try {\n $objects = $this->queryHandler->search($sosl);\n\n $data = $this->convertCrmData($objects, $userId);\n\n return ! empty(array_filter($data)) ? $data : null;\n } catch (NoResultsException) {\n return null;\n }\n }\n\n public function matchByPhone(string $phone, ?string $rawPhoneNumber = null, ?int $userId = null): ?array\n {\n // Don't bother looking up numbers that are masked.\n if (str_contains($phone, '**')) {\n return null;\n }\n\n if ($this->isPhoneNumberOfTeamMember($phone)) {\n return null;\n }\n\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $phoneNational = phone_national(null, $phone) ?? '';\n $possiblePhoneFormats = collect([\n preg_replace('/\\D/', '', ltrim($phone, '0+')),\n preg_replace('/\\D/', '', $phoneNational),\n formatDashPhoneNumber($phone),\n $phoneNational,\n ])\n ->filter() // Removes null and empty strings\n ->unique()\n ->values();\n\n foreach ($possiblePhoneFormats as $phone) {\n $sosl = $queryBuilder->buildMatchByQuery($phone, Field::TYPE_PHONE);\n if ($sosl === null) {\n continue;\n }\n\n try {\n $objects = $this->queryHandler->search($sosl);\n $objects = $this->queryHandler->prioritiseResults(\n $objects,\n $phone,\n QueryHandler::PRIORITISE_PHONE\n );\n\n return $this->convertCrmData($objects, $userId);\n } catch (NoResultsException) {\n continue;\n }\n }\n\n return null;\n }\n\n private function isPhoneNumberOfTeamMember(string $phone): bool\n {\n $teamRepository = app(TeamRepository::class);\n $user = $teamRepository->findTeamMemberByPhone($this->team, $phone);\n\n if ($user instanceof User) {\n return true;\n }\n\n return false;\n }\n\n protected function getCacheKey(string $object, ?int $userId = null): ?string\n {\n $key = $this->profile->id . $object;\n $keySuffix = $this->getOwnerKeySuffix($userId);\n\n return $key . $keySuffix;\n }\n\n private function getOwnerKeySuffix(?int $userId = null): string\n {\n return $userId === null ? '' : (string) $userId;\n }\n\n /** Determine the CRM Objects which represent the call activity. */\n public function matchByName(string $name, ?int $userId = null): ?array\n {\n // Don't waste time searching for single character strings.\n if (\\strlen($name) <= 1) {\n return null;\n }\n\n if ($this->profile === null) {\n return null;\n }\n\n $cacheKey = $this->getCacheKey($name, $userId);\n\n $result = Cache::remember($cacheKey, 60, function () use ($name, $userId) {\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $sosl = $queryBuilder->buildMatchByQuery($name, 'name');\n if ($sosl === null) {\n return false;\n }\n\n try {\n $objects = $this->queryHandler->search($sosl);\n } catch (NoResultsException $e) {\n return false;\n }\n\n $objects = $this->queryHandler->prioritiseResults(\n $objects,\n $name,\n QueryHandler::PRIORITISE_NAME\n );\n\n $data = $this->convertCrmData($objects, $userId);\n\n return (! empty(array_filter($data))) ? $data : false;\n });\n\n return is_array($result) ? $result : null;\n }\n\n /**\n * @return array{\n * Lead|null,\n * Account|null,\n * Opportunity|null,\n * Contact|null,\n * Stage|null,\n * string|null\n * }\n */\n protected function convertCrmData(QueryIterator $objects, ?int $userId = null): array\n {\n $lead = null;\n $contact = null;\n $opportunity = null;\n $account = null;\n $stage = null;\n $countryCode = null;\n\n if ($objects->count() > 0) {\n $object = $objects->current();\n\n if ($object['attributes']['type'] === 'Lead') {\n $lead = $this->importLead($object);\n\n // Lead might not be imported if the Stage is null for example.\n if ($lead) {\n $countryCode = $lead->country_code;\n $stage = $lead->stage;\n }\n } else {\n if ($object['attributes']['type'] === 'Contact') {\n $contact = $this->importContact($object);\n $account = $contact->account;\n } else {\n $account = $this->importAccount($object);\n }\n\n if ($contact && $contact->country_code) {\n $countryCode = $contact->country_code;\n } elseif ($account) {\n $countryCode = $account->country_code;\n }\n\n try {\n $sfOpportunities = $this->findOpportunities(\n $account?->getCrmProviderId(),\n $contact?->getCrmProviderId(),\n $userId\n );\n\n // Take the first opportunity, which will be ordered as priority based on their settings.\n if (! empty($sfOpportunities)) {\n // Persist this remote object.\n $opportunity = $this->syncOpportunity($sfOpportunities[0]['crmId']);\n $stage = $opportunity?->stage;\n }\n } catch (Exception) {\n // Nothing to see here.\n }\n }\n }\n\n return [\n $lead,\n $account,\n $opportunity,\n $contact,\n $stage,\n $countryCode,\n ];\n }\n\n /**\n * @inheritdoc\n */\n public function updateStage($crmObject, Stage $stage): void\n {\n if ($stage->type === Stage::TYPE_LEAD) {\n $objectType = 'Lead';\n $objectId = $crmObject->crm_provider_id;\n $objectStageType = 'Status';\n } else {\n $objectType = 'Opportunity';\n $objectId = $crmObject->crm_provider_id;\n $objectStageType = 'StageName';\n }\n\n $headers = [];\n if ($this->config->trigger_assignment_rules === false) {\n // @see: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/headers_autoassign.htm\n $headers = [\n 'Sforce-Auto-Assign' => 'false',\n ];\n }\n\n $this->updateRecord($objectType, $objectId, [$objectStageType => $stage->name], $headers);\n }\n\n public function parseObjectType(string $objectId): string\n {\n if (Str::startsWith($objectId, '001')) {\n return 'account';\n }\n\n if (Str::startsWith($objectId, '003')) {\n return 'contact';\n }\n\n if (Str::startsWith($objectId, '00Q')) {\n return 'lead';\n }\n\n if (Str::startsWith($objectId, '006')) {\n return 'opportunity';\n }\n\n if (Str::startsWith($objectId, '00U')) {\n return 'event';\n }\n\n if (Str::startsWith($objectId, '00T')) {\n return 'task';\n }\n\n throw new \\InvalidArgumentException('Unsupported Object Type');\n }\n\n public function syncProfiles(?User $userToSearch = null): ?Profile\n {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, ['profile' => $this->profile]);\n $query = $queryBuilder->buildGetUsersQuery($userToSearch);\n\n try {\n $salesforceUsers = $this->queryHandler->query($query, [\n 'active' => true,\n ]);\n } catch (NoResultsException $e) {\n $this->logger->info('[Salesforce] Sync Profiles. No users found', [\n 'query' => $query,\n 'error' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $teamRepository = app(TeamRepository::class);\n $customRules = $this->getCustomProfileRules($teamRepository);\n\n foreach ($salesforceUsers as $crmUser) {\n if ($crmUser['Email'] === null) {\n continue;\n }\n\n if (! $this->customProfileValidation($crmUser, $customRules)) {\n continue;\n }\n\n $user = $teamRepository->findActiveTeamMemberByEmail($this->team, $crmUser['Email']);\n\n if (! $user instanceof User) {\n continue;\n }\n\n $edition = $crmUser['UserPreferencesLightningExperiencePreferred']\n ? Profile::EDITION_LIGHTNING\n : Profile::EDITION_CLASSIC;\n\n $profileRepository = app(ProfileRepository::class);\n $profile = $profileRepository->updateOrCreateProfile(\n $user,\n [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmUser['Id'],\n ],\n [\n 'user_id' => $user->getId(),\n 'edition' => $edition,\n 'has_external_cti' => ! empty($crmUser['CallCenterId']),\n 'crm_profile_id' => $crmUser['ProfileId'],\n ]\n );\n\n if ($userToSearch instanceof User && $userToSearch->getId() === $user->getId()) {\n return $profile;\n }\n }\n\n // Clean up inactive profiles\n try {\n $this->archiveInactiveProfiles();\n } catch (\\Exception $e) {\n $this->logger->warning('[Salesforce] Profile archiving failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $e->getMessage(),\n ]);\n }\n\n return null;\n }\n\n public function generateProviderUrl(string $providerId, string $objectType): ?string\n {\n $url = null;\n\n // For Salesforce it's easy, we just point every object to the apex domain and they handle it.\n switch ($objectType) {\n case 'lead':\n case 'account':\n case 'contact':\n case 'opportunity':\n case 'task':\n case 'event':\n case 'activity':\n\n $url = $this->config->crm_base_url . '/' . $providerId;\n\n break;\n }\n\n return $url;\n }\n\n public function buildTaskSearchFields(): array\n {\n return ['Id', 'WhoId', 'WhatId', 'AccountId'];\n }\n\n public function getTaskByFilterConditions(\n array $fields,\n array $filters,\n bool $bulkSearch = false,\n bool $strictFilters = true\n ): ?array {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $query = $queryBuilder->buildSearchTaskQuery($fields, $filters, $bulkSearch, $strictFilters);\n\n try {\n if (! $bulkSearch) {\n $objects = $this->queryHandler->query($query, $filters);\n if ($objects->count() === 1) {\n return $objects->current();\n }\n }\n\n if ($bulkSearch) {\n $objects = $this->queryHandler->query($query);\n $records = [];\n foreach ($objects as $record) {\n $key = $record[end($fields)];\n $records[$key] = $record;\n }\n\n return $records;\n }\n } catch (\\Exception $e) {\n $this->logger->info('[Salesforce] Failed to execute query', [\n 'query' => $query,\n 'error' => $e->getMessage(),\n ]);\n }\n\n return null;\n }\n\n public function mapCrmObjects(array $task): array\n {\n $activityData = [];\n\n if (! empty($task['WhoId'])) {\n $type = $this->parseObjectType($task['WhoId']);\n $activityData[$type] = $task['WhoId'];\n }\n if (! empty($task['AccountId'])) {\n $activityData['account'] = $task['AccountId'];\n }\n if (! empty($task['WhatId'])) {\n $activityData['opportunity'] = $task['WhatId'];\n }\n\n return $activityData;\n }\n\n /**\n * Get SF task by Outreach call id.\n */\n public function getTaskByFilter(\n string $activityFieldType,\n array $filters,\n string $operator = '=',\n array $additionalFields = []\n ): ?array {\n $data = [];\n\n try {\n // Default (base) fields.\n $fields = ['Id', 'Subject', 'Description', 'ActivityDate', 'WhoId', 'WhatId', $activityFieldType];\n\n foreach ($additionalFields as $additionalField) {\n $fields[] = $additionalField->crm_provider_id;\n }\n\n $fields = array_unique($fields);\n\n // Find task with the same Outreach id as the call id.\n $query = 'SELECT ' . implode(',', $fields) . '\n FROM Task\n WHERE IsArchived = false AND IsDeleted = false';\n\n foreach ($filters as $key => $value) {\n $key = preg_quote($key, '/');\n $key = str_replace(['\\'', '\"'], '', $key);\n // Prepare the substitution.\n $strKey = \":$key\";\n\n $query .= \" AND $key $operator $strKey\";\n }\n\n $query .= ' ORDER BY LastModifiedDate DESC LIMIT 1';\n\n $objects = $this->queryHandler->query($query, $filters);\n\n // There should be only one task related to this call if any.\n if ($objects->count() === 1) {\n $object = $objects->current();\n\n $dueDate = $object['ActivityDate'] ? Carbon::parse($object['ActivityDate'])->toIso8601String() : null;\n\n $data = array_merge($object, [\n 'crmId' => $object['Id'],\n 'subject' => $object['Subject'],\n 'summary' => $object['Description'],\n 'due' => $dueDate,\n 'Type' => $object[$activityFieldType],\n ]);\n }\n } catch (NoResultsException $e) {\n // Filters don't match any records.\n } catch (ServiceUnavailableException $serviceUnavailableException) {\n // Service cannot be queried. We should probably log this.\n }\n\n return $data;\n }\n\n /**\n * Get Salesforce fields including datetime fields\n *\n * @param $objectType\n */\n private function getAllFieldsAsArray($objectType): array\n {\n $basicFields = [];\n // Not all users have access to all object fields.\n if ($this->profile->{$objectType . '_fields'}) {\n $basicFields = explode(',', $this->profile->{$objectType . '_fields'});\n }\n\n $extraFields = [\n 'CreatedDate',\n 'LastModifiedDate',\n 'IsDeleted',\n ];\n\n if ($objectType === self::OBJECT_OPPORTUNITY\n && $this->config->opportunity_value_field_id\n && ! in_array($this->config->opportunityValueField->crm_provider_id, $basicFields)\n ) {\n $extraFields[] = $this->config->opportunityValueField->crm_provider_id;\n }\n\n return array_unique(array_merge($basicFields, $extraFields));\n }\n\n /**\n * Generate transcription for activity description.\n */\n private function generateTranscription(Activity $activity): string\n {\n if (! ($this->config->store_transcript)) {\n // If sending transcription to activity toggle is disabled\n return '';\n }\n\n return $this->transcriptionService\n ->findTranscriptionByActivity($activity)\n ->map(static function (array $transcriptionSegment): string {\n return $transcriptionSegment['formattedStartsAt'] . ' | ' . $transcriptionSegment['transcript'];\n })\n ->implode(PHP_EOL);\n }\n\n /**\n * Find related Salesforce event based on activity data\n *\n * @return array<string>\n */\n public function fetchRelatedActivity(Activity $activity): array\n {\n $this->logger->info('[Salesforce] Searching for related activity', [\n 'activityId' => $activity->getUuid(),\n 'ownerId' => $this->profile?->crm_provider_id,\n ]);\n\n $sfEvent = $this->fetchRelatedEvent($activity);\n if (empty($sfEvent)) {\n $this->logger->info('[Salesforce] No related activity found', [\n 'activityId' => $activity->getUuid(),\n 'ownerId' => $this->profile?->crm_provider_id,\n 'account' => $activity->hasAccount()\n ? $activity->getAccount()->getCrmProviderId()\n : null,\n ]);\n\n return [];\n }\n\n return $sfEvent;\n }\n\n public function fetchAndAssociateRelatedActivity(Activity $activity): ?Activity\n {\n if ($activity->isTypeConference() === false) {\n return null;\n }\n\n if ($activity->hasActualStartTime() === false && $activity->hasScheduledStartTime() === false) {\n return null;\n }\n\n if (! $activity->hasProspect()) {\n $this->logger->info('[Salesforce] Skip look up, Activity not linked to Lead, Contact or Account', [\n 'activityId' => $activity->getUuid(),\n ]);\n\n return null;\n }\n\n $playbook = $this->getPlaybook($activity->getUser());\n if ($playbook !== null && $playbook->getActivityType() === Playbook::ACTIVITY_TYPE_TASK) {\n $this->logger->info('[Salesforce] Skip auto-sync for task-based playbook', [\n 'activityUuid' => $activity->getUuid(),\n 'playbookId' => $playbook->getId(),\n 'playbookType' => $playbook->getActivityType(),\n ]);\n\n return null;\n }\n\n try {\n $sfEvent = $this->fetchRelatedActivity($activity);\n if (empty($sfEvent)) {\n return null;\n }\n\n [$activityField, $activityType] = $this->resolveActivityTypeFromEvent($activity, $sfEvent);\n\n $this->logger->info('[Salesforce] Found related activity', [\n 'activityId' => $activity->getUuid(),\n 'sfEvent' => $sfEvent['Id'],\n 'activityFieldName' => $activityField,\n 'crmActivityType' => ($activityField !== null && isset($sfEvent[$activityField]))\n ? $sfEvent[$activityField]\n : null,\n 'activityType' => $activityType,\n ]);\n\n $userId = $this->findRelatedActivityUserId($activity, $sfEvent);\n\n if ($activity->getUserId() !== $userId) {\n $this->logger->info('[Salesforce] Updating meeting owner', [\n 'activityId' => $activity->getUuid(),\n 'oldUserId' => $activity->getUserId(),\n 'newUserId' => $userId,\n ]);\n }\n\n $this->updateSfEventDescription($activity, $sfEvent);\n\n $activity->update([\n 'user_id' => $userId,\n 'crm_provider_id' => $sfEvent['Id'],\n 'playbook_category_id' => $activityType->id ?? $activity->getCategory()?->getId(),\n ]);\n\n $this->logger->info('[Salesforce] Activity updated', [\n 'activityId' => $activity->getUuid(),\n ]);\n\n return $activity;\n } catch (\\Exception $exception) {\n \\Sentry::captureException($exception);\n\n throw $exception;\n }\n }\n\n /**\n * @param array<string, mixed> $sfEvent\n *\n * @return array{0: string|null, 1: mixed}\n */\n private function resolveActivityTypeFromEvent(Activity $activity, array $sfEvent): array\n {\n $activityField = $this->getActivityFieldName($activity);\n $activityType = null;\n\n if ($activityField !== null && ! empty($sfEvent[$activityField])) {\n $playbook = $this->getPlaybook($activity->getUser());\n $activityType = $this->getPlaybookCategory($playbook, strval($sfEvent[$activityField]));\n }\n\n return [$activityField, $activityType];\n }\n\n /**\n * @param array<string> $sfEvent\n */\n private function findRelatedActivityUserId(Activity $activity, array $sfEvent): int\n {\n $userId = $activity->getUserId();\n\n if (empty($sfEvent['OwnerId']) === false) {\n $profile = $this\n ->config\n ->profiles()\n ->where('crm_provider_id', $sfEvent['OwnerId'])\n ->get()\n ->filter(static function (Profile $profile) use ($activity): bool {\n if (! $activity->isTypeConference()) {\n return ! empty($profile->user) ? $profile->user->isStatusActive() : false;\n }\n\n $participants = $activity->getParticipants();\n\n return ! empty($profile->user)\n ? $profile->user->isStatusActive()\n && $profile->user->hasPermission(PermissionEnum::RECORD_MEETING)\n && $participants->contains('user_id', $profile->user_id)\n : false;\n })\n ->first();\n\n if ($profile) {\n $userId = $profile->user_id;\n }\n }\n\n return $userId;\n }\n\n /**\n * @param array<string, mixed> $sfEvent\n */\n private function updateSfEventDescription(Activity $activity, array $sfEvent): void\n {\n try {\n if (str_contains($sfEvent['Description'], $activity->id_string)) {\n return;\n }\n\n $payload = [\n 'Description' => $sfEvent['Description']\n . PHP_EOL\n . PHP_EOL\n . (new DecorateActivity())->generateDescription($activity),\n ];\n\n $this->logger->info('[Salesforce] Update record', [\n 'activityId' => $activity->getUuid(),\n 'sfEvent' => $sfEvent['Id'],\n 'payload' => $payload,\n ]);\n\n $payload = array_merge($payload, $this->fetchCustomFieldData($activity, Field::OBJECT_EVENT));\n\n $this->updateRecord('Event', $sfEvent['Id'], $payload);\n } catch (\\Exception) {\n $this->logger->error('[Salesforce] Failed to update record', [\n 'activityUuid' => $activity->getUuid(),\n 'sfEvent' => $sfEvent['Id'],\n ]);\n }\n }\n\n /**\n * Returns the most recently modified Event within time range (if any).\n *\n * @return array|null An Event record from Salesforce.\n */\n private function fetchRelatedEvent(Activity $activity): ?array\n {\n $ownerId = $this->profile?->crm_provider_id;\n if ($ownerId === null) {\n return [];\n }\n\n /** @var ?Carbon $from */\n /** @var ?Carbon $to */\n [$from, $to] = $this->getFromToDates($activity);\n\n try {\n $whoId = null;\n $hasWho = $activity->lead_id || $activity->contact_id;\n if ($hasWho) {\n $whoId = $activity->hasLead()\n ? $activity->getLead()->crm_provider_id\n : $activity->getContact()->crm_provider_id;\n }\n\n if ($hasWho === false && $activity->account_id === null) {\n return null;\n }\n\n $query = $this->buildFetchRelatedEventQuery($activity);\n\n $objects = $this->queryHandler->query($query, [\n 'ownerId' => $ownerId,\n 'whoId' => $whoId,\n 'whatId' => $activity->hasOpportunity() ? $activity->getOpportunity()->crm_provider_id : null,\n 'accountId' => $activity->hasAccount() ? $activity->getAccount()->crm_provider_id : null,\n 'from' => $from?->format('Y-m-d\\TH:i:s\\Z'),\n 'to' => $to?->format('Y-m-d\\TH:i:s\\Z'),\n ]);\n\n foreach ($objects as $object) {\n return $object;\n }\n } catch (NoResultsException $e) {\n return [];\n }\n\n return [];\n }\n\n private function getFromToDates(Activity $activity): array\n {\n $from = null;\n $to = null;\n\n /** @var ?CalendarEvent $calendarEvent */\n $calendarEvent = $activity->calendarEvent()->first();\n if ($calendarEvent !== null) {\n $from = $calendarEvent->getStartTime();\n $to = $calendarEvent->getEndTime();\n }\n\n // For non-calendar imported activities\n // Also double check if calendar event dates could be null?\n // If null use what we've got so far\n if ($from === null || $to === null) {\n $from = $activity->hasScheduledStartTime()\n ? $activity->getScheduledStartTime()\n : $activity->getActualStartTime();\n $to = $activity->hasScheduledEndTime()\n ? $activity->getScheduledEndTime()->addMinutes(15)\n : $activity->getActualEndTime();\n }\n\n return [$from, $to];\n }\n\n /**\n * Determines the appropriate activity field name for querying Salesforce events.\n *\n * This method follows a hierarchy to determine the field name:\n * 1. Uses the playbook's activity field if it exists and is in the profile's accessible fields\n * 2. Falls back to the default activity field if the profile has no event fields configured\n * 3. Returns null if no suitable field is found\n *\n * @param Activity $activity The activity to determine the field for\n *\n * @return string|null The field name to use in queries, or null if none is available\n */\n private function getActivityFieldName(Activity $activity): ?string\n {\n if ($this->profile === null) {\n $this->logger->warning('[Salesforce] Cannot determine activity field - profile not found', [\n 'activityId' => $activity->getUuid(),\n ]);\n\n return null;\n }\n\n $profileEventFields = $this->profile->getFieldsAsArray('event');\n\n if (empty($profileEventFields)) {\n $defaultActivityField = $this->getDefaultActivityField(Field::OBJECT_EVENT);\n $defaultFieldName = $defaultActivityField?->getAttribute('crm_provider_id');\n // Profile not yet synced — fall back to the default activity field.\n // There is a small chance that the profile won't have Default Activity Type field access\n // in which case the query will fail.\n // This is however an edge case and should be reviewed for profile sync issues.\n Sentry::withScope(function (\\Sentry\\State\\Scope $scope) use ($defaultFieldName): void {\n $scope->setContext('details', [\n 'profileId' => $this->profile->id,\n 'defaultField' => $defaultFieldName,\n ]);\n Sentry::captureMessage(\n '[Salesforce] Profile event fields empty, falling back to default activity field.',\n \\Sentry\\Severity::warning()\n );\n });\n\n return $defaultFieldName;\n }\n\n $playbook = $this->getPlaybook($activity->getUser());\n\n if (! is_null($playbook) && ! is_null($playbook->getActivityField())) {\n $playbookFieldName = $playbook->getActivityField()->getAttribute('crm_provider_id');\n\n if (in_array($playbookFieldName, $profileEventFields, true)) {\n return $playbookFieldName;\n }\n\n $this->logger->warning('[Salesforce] Playbook activity field not found in profile fields', [\n 'activityId' => $activity->getUuid(),\n 'playbookField' => $playbookFieldName,\n 'profileId' => $this->profile->id,\n ]);\n }\n\n return null;\n }\n\n private function buildFetchRelatedEventQuery(Activity $activity): string\n {\n $hasWho = $activity->lead_id || $activity->contact_id;\n\n $activityFieldName = $this->getActivityFieldName($activity);\n $fields = array_filter(['Id', 'Description', 'OwnerId', $activityFieldName]);\n\n $ownerCondition = '(OwnerId = :ownerId OR CreatedById = :ownerId)';\n\n $query = '\n SELECT ' . implode(',', $fields) . '\n FROM Event\n WHERE ' . $ownerCondition . '\n AND IsArchived = false\n AND IsAllDayEvent = false\n AND StartDateTime >= :from\n AND EndDateTime <= :to\n AND (';\n\n $operator = '';\n if ($activity->account_id) {\n // This covers events tied to a related contact or opportunity too.\n $query .= 'AccountId = :accountId';\n\n $operator = ' OR ';\n }\n\n if ($hasWho) {\n $query .= $operator . 'WhoId = :whoId';\n\n // If we are also going to check on a specific opportunity, set that up.\n if ($activity->opportunity_id) {\n $query .= ' OR WhatId = :whatId';\n }\n }\n\n $query .= ') ORDER BY LastModifiedDate DESC';\n\n return $query;\n }\n\n public function fetchProspect(array $task): array\n {\n $lead = $account = $opportunity = $contact = $stage = $countryCode = null;\n $externalId = $task['WhoId'] ?? null;\n\n // Lead or Contact\n if ($externalId) {\n try {\n [$lead, $account, $opportunity, $contact, $stage, $countryCode] = $this->parseRecords($externalId);\n } catch (\\InvalidArgumentException $exception) {\n // Invalid object type.\n }\n }\n\n // If we happen to know the opportunity or account from the Task, figure that out.\n if (empty($task['WhatId']) === false) {\n // WhatId could be either Account ID or Opportunity ID.\n // If WhatId is Opportunity ID, get the opportunity and stage from the CRM.\n try {\n [, $account, $opportunity, , $stage, ] = $this->parseRecords($task['WhatId']);\n } catch (\\InvalidArgumentException $exception) {\n // Invalid object type.\n }\n }\n\n return [$lead, $account, $opportunity, $contact, $stage, $countryCode];\n }\n\n /**\n * Save activity transcription summary as note\n */\n public function saveTranscriptionSummaryAsNote(\n ActivityContract $activity,\n string $title,\n string $body,\n ?string $objectId,\n ?NoteObject $noteObject = null,\n ): ?string {\n return $this->saveNote($title, $body, (string) $objectId);\n }\n\n public function getObjectByFilterConditions(string $objectType, array $fields, array $filters): ?array\n {\n if ($this->profile === null) {\n return null;\n }\n\n $queryBuilder = app(QueryBuilder::class, [\n 'profile' => $this->profile,\n ]);\n\n $query = $queryBuilder->buildObjectSearchQuery($objectType, $fields, $filters);\n\n try {\n $objects = $this->queryHandler->query($query, $filters);\n if ($objects->count() === 1) {\n return $objects->current();\n }\n } catch (\\Exception $e) {\n $this->logger->info('[Salesforce] Failed to execute query', [\n 'query' => $query,\n 'error' => $e->getMessage(),\n ]);\n }\n\n return null;\n }\n\n private function getCustomProfileRules(TeamRepository $teamRepository): array\n {\n $teamSettings = $teamRepository->getTeamSetting($this->team, 'custom_profile_validation');\n\n if ($teamSettings instanceof TeamSettings && $teamSettings->getValueType() === 'array') {\n $customRules = json_decode($teamSettings->getValue(), true);\n if (is_array($customRules)) {\n return $customRules;\n }\n }\n\n return [];\n }\n\n private function customProfileValidation(array $crmUser, array $customRules): bool\n {\n foreach ($customRules as $customRule) {\n if ($crmUser[$customRule['field']] !== $customRule['value']) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * When syncing Contact / Lead / Account / Opportunity / Stage crm entities,\n * validate and restore locally trashed objects,\n * before updating them. Objects are identified by CrmProviderId\n */\n private function restoreAnyTrashedEntity(HasMany $targetEntity, string $crmProviderId): void\n {\n $recordExists = $targetEntity->withTrashed()->where(['crm_provider_id' => $crmProviderId])->first();\n if ($recordExists && $recordExists->trashed()) {\n $recordExists->restore();\n }\n }\n\n #[\\Override] public function supportsNotes(): bool\n {\n return true;\n }\n\n private function getOwnerProfile(?string $ownerId): ?Profile\n {\n if ($ownerId === null) {\n return null;\n }\n\n return $this->config->profiles()\n ->where('crm_provider_id', $ownerId)\n ->first();\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.5265958,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.53523934,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.5462101,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.55485374,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.56349736,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.5744681,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.58543885,"top":0.074221864,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.61203456,"top":0.074221864,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.62300533,"top":0.074221864,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.9587766,"top":0.074221864,"width":0.02825798,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"27","depth":4,"bounds":{"left":0.9162234,"top":0.09896249,"width":0.009973404,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.9281915,"top":0.09896249,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"23","depth":4,"bounds":{"left":0.9381649,"top":0.09896249,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.95046544,"top":0.09896249,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"105","depth":4,"bounds":{"left":0.96043885,"top":0.09896249,"width":0.011968086,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9740692,"top":0.09736632,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.98138297,"top":0.09736632,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-6552009829845400550
|
-7851948309243295355
|
click
|
accessibility
|
NULL
|
"LeadConverted" not found, press ⌘G to sea "LeadConverted" not found, press ⌘G to search from the top
text/html
text/html
text/html
Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LeadConverted
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
4/4
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Sync Changes
Hide This Notification
Code changed:
Hide
19
144
3
22
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Crm\Salesforce;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Jiminny\Component\Country\CountriesMap;
use Jiminny\Contracts\Acl\PermissionEnum;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Services\Crm\FetchRelatedActivityInterface;
use Jiminny\Contracts\Services\Crm\LayoutManagementInterface;
use Jiminny\Contracts\Services\Crm\MatchCrmEntitiesInterface;
use Jiminny\Contracts\Services\Crm\Provider\SalesforceBatchSyncInterface;
use Jiminny\Contracts\Services\Crm\Provider\SalesforceInterface;
use Jiminny\Contracts\Services\Crm\RemoteEntityLookupInterface;
use Jiminny\Contracts\Services\Crm\RemoteEntityManipulationInterface;
use Jiminny\Contracts\Services\Crm\RemoteNoteEntityManipulationInterface;
use Jiminny\Contracts\Services\Crm\SearchTaskInterface;
use Jiminny\Contracts\Services\Crm\SendSummaryToCrmInterface;
use Jiminny\Contracts\Services\Crm\SettingsInterface;
use Jiminny\Contracts\Services\Crm\SupportsObjectTypeParseInterface;
use Jiminny\Contracts\Services\Crm\SyncCrmEntitiesInterface;
use Jiminny\Contracts\Services\Crm\SyncCrmProfileRecordTypesInterface;
use Jiminny\Contracts\Services\Crm\VerifyTaskExistsInterface;
use Jiminny\Enums\CrmObject;
use Jiminny\Events\Activities\Crm\LeadConverted;
use Jiminny\Events\Activities\Crm\ActivityLeadConverted;
use Jiminny\Exceptions\CrmException;
use Jiminny\Exceptions\HttpBadRequestException;
use Jiminny\Exceptions\HttpNotFoundException;
use Jiminny\Exceptions\NoResultsException;
use Jiminny\Exceptions\ServiceUnavailableException;
use Jiminny\Jobs\Crm\NoteObject;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Account;
use Jiminny\Models\Activity;
use Jiminny\Models\Calendar\CalendarEvent;
use Jiminny\Models\Contact;
use Jiminny\Models\Contracts\ActivityContract;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Models\Crm\ContactRole;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\Profile;
use Jiminny\Models\Crm\RecordType;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Lead;
use Jiminny\Models\Opportunity;
use Jiminny\Models\Playbook;
use Jiminny\Models\SocialAccount;
use Jiminny\Models\Stage;
use Jiminny\Models\TeamSettings;
use Jiminny\Models\User;
use Jiminny\Repositories\Crm\ContactRoleRepository;
use Jiminny\Repositories\Crm\FieldDataRepository;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\Crm\ProfileRepository;
use Jiminny\Repositories\Crm\RecordTypeFieldValuesRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\BaseService;
use Jiminny\Services\Crm\Helpers\ArrayIterator;
use Jiminny\Services\Crm\MatchDomainByEmailInterface;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Services\Crm\ResolveCompanyNameByEmailTrait;
use Jiminny\Services\Crm\Salesforce\Fields\FieldTypeConverter;
use Jiminny\Services\Crm\Salesforce\Fields\ValueNormalizer;
use Jiminny\Services\Crm\Salesforce\ServiceTraits\RecordManipulationsTrait;
use Jiminny\Services\Crm\Salesforce\ServiceTraits\SyncFieldsTrait;
use Jiminny\Utils\CurrencyFormatter;
use Jiminny\Utils\StringUtil;
use Ramsey\Uuid\Uuid;
use Sentry\Laravel\Facade as Sentry;
class Service extends BaseService implements
SalesforceInterface,
SalesforceBatchSyncInterface,
SyncCrmEntitiesInterface,
SyncCrmProfileRecordTypesInterface,
RemoteEntityManipulationInterface,
FetchRelatedActivityInterface,
SendSummaryToCrmInterface,
MatchDomainByEmailInterface,
SearchTaskInterface,
LayoutManagementInterface,
SettingsInterface,
MatchCrmEntitiesInterface,
RemoteEntityLookupInterface,
SupportsObjectTypeParseInterface,
RemoteNoteEntityManipulationInterface,
VerifyTaskExistsInterface
{
use ResolveCompanyNameByEmailTrait;
use SyncFieldsTrait;
use DeleteObjectsTrait;
use RecordManipulationsTrait;
use ServiceTraits\BatchSyncTrait;
/**
* Note Body Limit for the Old Note-Taking Tool
*
* @var int
*/
private const int CLASSIC_NOTE_MAX_LENGTH = 32000;
/**
* Note Content Limit for the New Notes
*
* @var int
*/
private const int ENHANCED_NOTE_MAX_LENGTH = 50000000;
private const string INSTALLED_PACKAGE_ID = '033Tw0000007bKbIAI';
private const int CACHE_TTL = 600;
private const int TASK_VERIFICATION_CACHE_TTL = 86400; // 1 day - 86400
/**
* @var Client
*/
protected $client;
private PayloadBuilder $payloadBuilder;
private QueryHandler $queryHandler;
private OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
public function __construct(
Client $client,
PayloadBuilder $payloadBuilder,
private readonly CountriesMap $countriesMap,
private readonly ProspectPhotoPathService $prospectPhotoPathService,
) {
parent::__construct();
$this->client = $client;
$this->payloadBuilder = $payloadBuilder;
$this->queryHandler = app(QueryHandler::class, [
'client' => $this->client,
'logger' => $this->logger,
]);
$this->opportunitySyncStrategyResolver = app(OpportunitySyncStrategyResolver::class, [
'client' => $this->client,
]);
}
public function getDisplayName(): string
{
return 'Salesforce';
}
public function getJobDelay(): int
{
return 1;
}
protected function getOAuthAccount(User $user): ?SocialAccount
{
return $user->getSocialAccount(SocialAccount::PROVIDER_SALESFORCE);
}
public function verifyTaskExists(Activity $activity): bool
{
$crmProviderId = $activity->getCrmProviderId();
$cacheKey = "crm_task_exists:{$this->config->getId()}:$crmProviderId";
return Cache::remember($cacheKey, self::TASK_VERIFICATION_CACHE_TTL, function () use ($activity, $crmProviderId) {
$playbook = $this->getPlaybookFromActivity($activity);
if ($playbook === null) {
$this->logger->warning('[Salesforce] Cannot verify task - no playbook found', [
'activity' => $activity->getId(),
'crm_provider_id' => $crmProviderId,
]);
return false;
}
$objectType = $playbook->getActivityType() === Playbook::ACTIVITY_TYPE_EVENT ? 'Event' : 'Task';
try {
$record = $this->getRecord($objectType, $crmProviderId, ['Id', 'IsDeleted']);
return ! empty($record) && ($record['IsDeleted'] ?? false) === false;
} catch (HttpNotFoundException|HttpBadRequestException) {
$this->logger->info('[Salesforce] Activity record not found during verification', [
'activity' => $activity->getId(),
'object_type' => $objectType,
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return false;
}
});
}
public function query(string $queryToRun, array $parameters = []): QueryIterator
{
// Due to poorly designed external calls, this method cannot be entirely removed
return $this->queryHandler->query($queryToRun, $parameters);
}
/*=========== Organization Information ===============*/
/**
* Get a list of all the API Versions for the instance.
*
* @throws CrmException
*
* @return mixed
*
*/
public function getApiVersions()
{
$url = $this->config->crm_base_url . '/services/data';
$response = $this->client->get($url);
return json_decode($response->getBody(), true);
}
/**
* Gets the valid recordTypes for a given Salesforce Object via the describe API.
*
* @param string $crmObject The name of the Salesforce object. i.e. Account or Contact
*
* @return array The API output, converted from JSON to an associative array.
*/
public function getRecordTypes(string $crmObject): array
{
$url = $this->client->getObjectsUrl() . $crmObject . '/describe';
$response = $this->client->get($url);
$jsonResponse = json_decode($response->getBody(), true);
$fields = [];
foreach ($jsonResponse['recordTypeInfos'] as $row) {
$fields[] = ['recordTypeId' => $row['recordTypeId'], 'default' => $row['defaultRecordTypeMapping']];
}
return $fields;
}
/**
* Convert raw field data into a format compatible with CRM APIs.
*/
public function normalizeValue(string $fieldType, string $fieldValue, bool $internal = false): string
{
return ValueNormalizer::normalize($fieldType, $fieldValue, $internal);
}
/**
* @inheritdoc
*/
public function getDefaultFields(string $activityType): array
{
$fields = [];
$defaultFields = ($activityType === Playbook::ACTIVITY_TYPE_TASK)
? FieldDefinitions::defaultTaskFields()
: FieldDefinitions::defaultEventFields();
// This lazy creates these fields if not already setup.
foreach ($defaultFields as $defaultField) {
$fields[] = $this->config->fields()->firstOrCreate($defaultField);
}
return $fields;
}
/**
* @inheritdoc
*/
public function getDefaultActivityField(string $activityType): Field
{
// Setup the activity field as the default Type.
/** @var Field $activityField */
$activityField = $this->config->fields()->where([
'crm_provider_id' => 'Type',
'object_type' => $activityType,
])->first();
return $activityField;
}
/**
* @inheritdoc
*/
public function getSupportedPlaybookTypes(): array
{
return [Playbook::ACTIVITY_TYPE_TASK, Playbook::ACTIVITY_TYPE_EVENT];
}
protected function getDefaultFollowupLayoutFields(string $activityType): array
{
$fields = [];
$fieldRepo = app(FieldRepository::class);
$fieldFilter = ($activityType === Playbook::ACTIVITY_TYPE_TASK)
? FieldDefinitions::taskFollowupFieldsFilter()
: FieldDefinitions::eventFollowupFieldsFilter();
foreach ($fieldFilter as $eachFilter) {
$field = $fieldRepo->findOneConfigurationFieldByProperties($this->config, $eachFilter);
// Only add the field if it is created, which it should be.
if ($field) {
$fields[] = $field;
}
}
return $fields;
}
public function getDealInsightsFields(): array
{
return FieldDefinitions::dealInsightsFields();
}
private function isCustomField(Field $field): bool
{
return substr($field->crm_provider_id, -\strlen('__c')) === '__c';
}
/**
* This one is now called only when ImportActivityTypes is triggered or SyncFieldMetadata executed manually
* Regular sync now uses SharedSyncFieldsTrait -> syncSingleObjectType
* Needs to be replaced later on
*/
public function syncField(Field $field): void
{
try {
if ($this->isCustomField($field)) {
$query = '
SELECT
Id, Metadata, TableEnumOrId
FROM
CustomField
WHERE
DeveloperName = :fieldName
AND
TableEnumOrId = :fieldType
AND
NamespacePrefix = :namespacePrefix';
// We need to constrain the field lookup to the object, in case it's used in multiple places.
$objectType = \in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true)
? 'activity'
: $field->object_type;
$sfFields = $this->queryHandler->metadata($query, [
'fieldName' => substr($field->crm_provider_id, 0, -\strlen('__c')),
'fieldType' => ucfirst($objectType),
// This is used to ensure we only consider the field within the org, not installed packages.
'namespacePrefix' => 'null',
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
// Sync field metadata.
$metadata = $sfField['Metadata'];
$field->description = mb_strimwidth($metadata['description'] ?? '', 0, 191);
$field->label = mb_strimwidth($metadata['label'] ?? '', 0, Field::LABEL_MAX_LENGTH);
$field->type = $this->convertFieldType($metadata['type'], $field->getEntityName());
$field->is_mandatory = ($metadata['required'] === true);
$field->length = $metadata['length'];
$field->default_value = mb_strimwidth(trim($metadata['defaultValue'] ?? '', '"'), 0, 191);
$field->save();
} else {
$query = '
SELECT
Id, DataType, DeveloperName, Label, Length, Description
FROM
FieldDefinition
WHERE
DurableId = :entityName';
$entityName = $field->getEntityName();
$sfFields = $this->queryHandler->metadata($query, [
'entityName' => $entityName,
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
$convertedType = $this->convertFieldType($sfField['DataType'], $entityName);
$label = mb_strimwidth($sfField['Label'], 0, Field::LABEL_MAX_LENGTH);
if ($field->isBusinessType()) {
$label = 'Opportunity Type';
}
$field->description = mb_strimwidth($sfField['Description'], 0, Field::DESCRIPTION_MAX_LENGTH);
$field->label = $label;
$field->type = $convertedType;
$field->length = $sfField['Length'];
$field->save();
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
private function convertFieldType(string $from, ?string $entityName = null): string
{
$converter = new FieldTypeConverter();
return $converter->convert($from, $entityName);
}
/**
* @inheritdoc
*/
public function importPicklistValues(Field $field): array
{
$values = [];
$fieldValues = [];
try {
if ($this->isCustomField($field)) {
$query = '
SELECT
Id, Metadata, TableEnumOrId
FROM
CustomField
WHERE
DeveloperName = :fieldName
AND
TableEnumOrId = :fieldType
AND
NamespacePrefix = :namespacePrefix';
// We need to constrain the field lookup to the object, in case it's used in multiple places.
$objectType = \in_array($field->object_type, [Field::OBJECT_TASK, Field::OBJECT_EVENT], true) ?
'activity' : $field->object_type;
$sfFields = $this->queryHandler->metadata($query, [
'fieldName' => substr($field->crm_provider_id, 0, -\strlen('__c')),
'fieldType' => ucfirst($objectType),
// This is used to ensure we only consider the field within the org, not installed packages.
'namespacePrefix' => 'null',
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
$valueSet = $sfField['Metadata']['valueSet'];
if ($valueSet['valueSetName'] === null) {
// Local picklist values can be obtained easily.
$picklistValues = $valueSet['valueSetDefinition']['value'];
} else {
// But for some fields, we just get the Global Value Picklist pointer so need to do more work.
$picklistValues = $this->importGlobalValuePicklistValues($valueSet['valueSetName']);
}
// Import all active values.
foreach ($picklistValues as $i => $sfFieldValue) {
// Setup default value.
if ($sfFieldValue['default']) {
$field->update(['default_value' => $sfFieldValue['valueName']]);
}
// This comes through as null if active (lol).
if ($sfFieldValue['isActive'] !== false) {
$values[] = [
'value' => $sfFieldValue['valueName'],
'label' => $sfFieldValue['valueName'],
'sequence' => $i,
'is_default' => $sfFieldValue['default'],
];
}
}
} else {
$objectFields = $this->getObjectFields($field->object_type);
$fieldId = $field->crm_provider_id;
// Only work with our field of interest.
$objectField = array_filter($objectFields, function ($item) use ($fieldId) {
return $item['name'] === $fieldId;
});
$objectField = array_shift($objectField);
if (empty($objectField['picklistValues']) === false) {
foreach ($objectField['picklistValues'] as $i => $sfFieldValue) {
// Skip inactive values.
if ($sfFieldValue['active'] === false) {
continue;
}
// Setup default value.
if ($sfFieldValue['defaultValue']) {
$field->update(['default_value' => $sfFieldValue['value']]);
}
$values[] = [
'value' => $sfFieldValue['value'],
'label' => $sfFieldValue['label'],
'sequence' => $i,
'is_default' => $sfFieldValue['defaultValue'],
];
}
}
}
$fieldsToPurge = $field->values()->get()->pluck('value')->toArray();
foreach ($values as $value) {
$value['value'] = substr($value['value'] ?? '', 0, 255);
$fieldValues[] = $field->values()->updateOrCreate([
'value' => $value['value'],
], $value);
// Remove this value from the ones we are going to purge.
if (($key = array_search($value['value'], $fieldsToPurge, true)) !== false) {
unset($fieldsToPurge[$key]);
}
}
// Delete the old values that are no longer used.
// Get IDs of the values to be deleted
$valuesToDelete = $field->values()->whereIn('value', $fieldsToPurge);
$valuesToDeleteIds = $valuesToDelete->pluck('id');
if (! $valuesToDeleteIds->isEmpty()) {
$recordTypeFieldValuesRepository = app(RecordTypeFieldValuesRepository::class);
$recordTypeFieldValuesRepository->deleteForCrmFieldValueIds($valuesToDeleteIds->toArray());
// Now safely delete from crm_field_values
$valuesToDelete->delete();
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
return $fieldValues;
}
/**
* Gets values from Global Value Picklists.
*/
private function importGlobalValuePicklistValues(string $picklistName): array
{
$query = '
SELECT
Metadata
FROM
GlobalValueSet
WHERE
DeveloperName = :picklistName
LIMIT 1';
try {
$sfValues = $this->queryHandler->metadata($query, [
'picklistName' => $picklistName,
]);
// There is always 1 result at this point.
$sfValue = $sfValues->current();
return $sfValue['Metadata']['customValue'];
} catch (NoResultsException $noResultsException) {
// Nothing returned.
return [];
}
}
/**
* @inheritdoc
*/
public function syncProfileRecordTypes(): void
{
$objectTypes = [
'lead',
'account',
'contact',
'opportunity',
'task',
'event',
];
foreach ($objectTypes as $objectType) {
try {
$crmRecordTypes = $this->getRecordTypes(ucfirst($objectType));
foreach ($crmRecordTypes as $crmRecordType) {
// If the record type is default and not the Master type, set this.
if ($crmRecordType['default'] && $crmRecordType['recordTypeId'] !== '012000000000000AAA') {
$recordType = $this->config->recordTypes()
->where('crm_provider_id', $crmRecordType['recordTypeId'])
->first();
if ($recordType) {
$this->profile->{$objectType . '_record_type_id'} = $recordType->id;
}
}
}
} catch (HttpNotFoundException $exception) {
Log::error('No access to ' . $objectType . ' object, skipping...');
// XXX: should we log this fact somewhere?
continue;
}
}
if ($this->profile->isDirty()) {
$this->profile->save();
}
}
/**
* Gets business processes.
*/
public function importBusinessProcesses(): void
{
$query = '
SELECT
Id, IsActive, Name, TableEnumOrId
FROM
BusinessProcess
WHERE
TableEnumOrId IN (\'Lead\',\'Opportunity\')';
try {
$sfProcesses = $this->queryHandler->query($query);
// Upsert all processes for the team.
foreach ($sfProcesses as $sfProcess) {
/** @var BusinessProcess $businessProcess */
$businessProcess = $this->config->businessProcesses()->updateOrCreate([
'crm_provider_id' => $sfProcess['Id'],
], [
'team_id' => $this->team->id,
'name' => $sfProcess['Name'],
'type' => $sfProcess['TableEnumOrId'] === 'Lead' ? 'lead' : 'opportunity',
'is_selectable' => $sfProcess['IsActive'],
]);
$this->importBusinessProcessStages($businessProcess);
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
/**
* Gets business process stages.
*/
public function importBusinessProcessStages(BusinessProcess $businessProcess): void
{
$query = '
SELECT
Metadata
FROM
BusinessProcess
WHERE
Id = :processId';
try {
$stages = [];
$sfProcessStages = $this->queryHandler->metadata($query, [
'processId' => $businessProcess->crm_provider_id,
]);
// There is always 1 result at this point.
$sfProcessStage = $sfProcessStages->current();
// Upsert all processes for the team.
foreach ($sfProcessStage['Metadata']['values'] as $sfProcessStage) {
$sanitizedName = urldecode($sfProcessStage['valueName']); // Must decode: "%2C" becomes "," etc.
$stage = $businessProcess->crm->stages()
// This MUST match on label because this API doesn't use API Name.
->where('label', $sanitizedName)
->where('type', $businessProcess->type)
->where('is_selectable', 1)
->first();
if ($stage) {
$stages[] = $stage->id;
}
}
$businessProcess->stages()->sync($stages);
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
/**
* Gets record types.
*/
public function importRecordTypes(): void
{
$query = '
SELECT
Id, IsActive, Name, BusinessProcessId, SobjectType
FROM
RecordType';
try {
$sfRecordTypes = $this->queryHandler->query($query);
// Upsert all record types for the process.
foreach ($sfRecordTypes as $sfRecordType) {
$businessProcess = null;
if ($sfRecordType['BusinessProcessId']) {
$businessProcess = $this->config->businessProcesses()
->where('crm_provider_id', $sfRecordType['BusinessProcessId'])
->first();
}
/** @var RecordType $recordType */
$recordType = $this->config->recordTypes()->updateOrCreate([
'crm_provider_id' => $sfRecordType['Id'],
], [
'team_id' => $this->team->id,
'type' => mb_strtolower($sfRecordType['SobjectType']),
'name' => $sfRecordType['Name'],
'is_selectable' => $sfRecordType['IsActive'],
'business_process_id' => $businessProcess->id ?? null,
]);
$this->importRecordTypeFieldValues($recordType);
}
} catch (NoResultsException $noResultsException) {
// Do nothing.
}
}
/**
* Import record type - field value mappings. This only works for standard fields.
*/
public function importRecordTypeFieldValues(RecordType $recordType): void
{
try {
$query = '
SELECT
Metadata
FROM
RecordType
WHERE
Id = :recordTypeId';
$sfFields = $this->queryHandler->metadata($query, [
'recordTypeId' => $recordType->crm_provider_id,
]);
// There is always 1 result at this point.
$sfField = $sfFields->current();
// Sync field metadata.
$picklists = $sfField['Metadata']['picklistValues'];
foreach ($picklists as $picklist) {
$field = $this->config->fields()->where([
'type' => Field::TYPE_PICKLIST,
'object_type' => $recordType->type,
'crm_provider_id' => $picklist['picklist'],
])->first();
if ($field) {
$fieldValues = [];
foreach ($picklist['values'] as $value) {
// Must decode: "%2C" becomes "," etc.
$fieldValue = $field->values()
->where('value', urldecode($value['valueName']))
->first();
if ($fieldValue) {
$fieldValues[] = $fieldValue->id;
}
}
$recordType->fieldValues()->sync($fieldValues);
}
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
}
/**
* @inheritdoc
*/
public function importStages(?array $types = null, ?string $missingStageName = null): ?Stage
{
$params = [];
$missingStage = null;
if ($types === null) {
$types = [Stage::TYPE_LEAD, Stage::TYPE_OPPORTUNITY];
}
foreach ($types as $type) {
if ($type === Stage::TYPE_LEAD) {
$query = '
SELECT
Id, ApiName, MasterLabel, SortOrder
FROM
LeadStatus';
} else {
$query = '
SELECT
Id, ApiName, MasterLabel, IsActive, SortOrder, DefaultProbability
FROM
OpportunityStage';
}
if ($missingStageName) {
$escapedStageName = ValueNormalizer::replaceQueryWithStringLiterals($missingStageName);
$query .= ' WHERE ApiName = :stageName';
$params = [
'stageName' => $escapedStageName,
];
}
try {
$sfStages = $this->queryHandler->query($query, $params);
} catch (NoResultsException $exception) {
$sfStages = [];
}
$missingStage = null;
// Upsert all stages for the team.
foreach ($sfStages as $sfStage) {
$selectable = true;
if (array_key_exists('IsActive', $sfStage)) {
$selectable = $sfStage['IsActive'];
}
$this->restoreAnyTrashedEntity($this->config->stages(), $sfStage['Id']);
$stage = $this->config->stages()->updateOrCreate([
'crm_provider_id' => $sfStage['Id'],
], [
'team_id' => $this->team->id,
'name' => mb_strimwidth($sfStage['ApiName'], 0, 50),
'label' => mb_strimwidth($sfStage['MasterLabel'], 0, 191),
'type' => $type,
'sequence' => $sfStage['SortOrder'] ?? 0,
'is_selectable' => $selectable,
'probability' => $sfStage['DefaultProbability'] ?? null,
]);
if ($missingStageName && $missingStageName === $sfStage['ApiName']) {
$missingStage = $stage;
}
}
if ($missingStageName && $missingStage === null) {
// If they requested a stage that still doesn't exist, it must be inactive so lazy create it.
$missingStage = $this->config->stages()->create([
'crm_provider_id' => Uuid::uuid4(),
'team_id' => $this->team->id,
'name' => mb_strimwidth($missingStageName, 0, 50),
'label' => mb_strimwidth($missingStageName, 0, 191),
'type' => $type,
'sequence' => 0,
'is_selectable' => 0,
]);
}
}
return $missingStage;
}
/**
* @inheritdoc
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
$syncCount = 0;
$fields = $this->getAllFieldsAsArray('lead');
if (\in_array('Id', $fields, true) === false) {
return $syncCount;
}
$query = '
SELECT ' . rtrim(implode(',', $fields), ',') . '
FROM Lead
WHERE LastModifiedDate > :since
ORDER BY LastModifiedDate ASC';
try {
$sfLeads = $this->queryHandler->query($query, [
'since' => $since->format('Y-m-d\TH:i:s\Z'),
]);
foreach ($sfLeads as $sfLead) {
// Only sync if previously imported.
if ($this->hasLead($sfLead['Id'])) {
$this->importLead($sfLead);
$syncCount++;
}
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
$this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::LEAD);
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
$fields = $this->getAllFieldsAsArray('lead');
$sfLead = $this->getRecord('Lead', $crmId, $fields);
return $this->importLead($sfLead);
}
private function importLead($crmData): ?Lead
{
/** @var ?Stage $stage */
$stage = null;
if (isset($crmData['Status'])) {
// Get the current stage.
$stage = $this->config
->stages()
->where('name', $crmData['Status'])
->where('type', Stage::TYPE_LEAD)
->first();
if ($stage === null) {
// Import it.
$stage = $this->importStages([Stage::TYPE_LEAD], $crmData['Status']);
}
}
// If we have no way of importing this, just return null :(
if ($stage === null) {
return null;
}
$countryCode = $crmData['CountryCode'] ?? null;
// Salesforce allows custom "countries" to be created. Disregard these.
if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {
$countryCode = null;
}
// If we have no country code, try to parse it from the country name.
if ($countryCode === null && empty($crmData['Country']) !== false) {
$countryCode = $this->convertCountryNameToCode($crmData['Country']);
}
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($crmData['Phone'] ?? '', 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
$mobilePhone = null;
if (empty($crmData['MobilePhone']) === false) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($crmData['MobilePhone'], 0, 25);
$mobilePhone = phone_e164($countryCode, $number);
}
$convertedDate = null;
$convertedAccount = null;
$convertedOpportunity = null;
$convertedContact = null;
if ($crmData['IsConverted'] == 'true') {
$convertedDate = $crmData['ConvertedDate'];
if (empty($crmData['ConvertedAccountId']) === false) {
$convertedAccount = $this->config
->accounts()
->where('crm_provider_id', $crmData['ConvertedAccountId'])
->first();
if ($convertedAccount === null) {
try {
$convertedAccount = $this->syncAccount($crmData['ConvertedAccountId']);
} catch (HttpNotFoundException $exception) {
// Probably the user has no permissions to access the converted data.
}
}
}
if (empty($crmData['ConvertedOpportunityId']) === false) {
$convertedOpportunity = $this->config
->opportunities()
->where('crm_provider_id', $crmData['ConvertedOpportunityId'])
->first();
if ($convertedOpportunity === null) {
try {
$convertedOpportunity = $this->syncOpportunity($crmData['ConvertedOpportunityId']);
} catch (HttpNotFoundException $exception) {
// Probably the user has no permissions to access the converted data.
}
}
}
if (empty($crmData['ConvertedContactId']) === false) {
$convertedContact = $this->team
->crm
->contacts()
->where('crm_provider_id', $crmData['ConvertedContactId'])
->first();
if ($convertedContact === null) {
try {
$convertedContact = $this->syncContact($crmData['ConvertedContactId']);
} catch (HttpNotFoundException $exception) {
// Probably the user has no permissions to access the converted data.
}
}
}
}
if (empty($crmData['Company'])) {
$company = 'Unknown';
} else {
$company = mb_strimwidth($crmData['Company'], 0, 191);
}
$domain = null;
if (empty($crmData['Website']) === false) {
$domain = mb_strimwidth($crmData['Website'], 0, 191);
$domain = StringUtil::resolveDomain($domain);
}
$createdDate = null;
if (empty($crmData['CreatedDate']) === false) {
$createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');
}
$profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);
$data = [
'team_id' => $this->team->id,
'user_id' => $profile?->user_id,
'owner_id' => $crmData['OwnerId'] ?? '',
'company' => $company,
'domain' => $domain,
'name' => $crmData['Name'] ? mb_strimwidth($crmData['Name'], 0, 191) : '',
'title' => $crmData['Title'] ? mb_strimwidth($crmData['Title'], 0, 128) : null,
'email' => $crmData['Email'] ? mb_strimwidth($crmData['Email'], 0, 80) : null,
'phone' => $parsedNumber['phone'],
'ext' => $parsedNumber['ext'] ?? null,
'mobile_phone' => $mobilePhone,
'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(
crmConfiguration: $this->config,
crmProviderId: $crmData['Id'],
modelType: Lead::class,
fileName: $crmData['Id'],
avatarText: $crmData['Name']
),
'stage_id' => $stage->id,
'record_type_id' => null,
'converted_at' => $convertedDate,
'converted_account_id' => $convertedAccount->id ?? null,
'converted_opportunity_id' => $convertedOpportunity->id ?? null,
'converted_contact_id' => $convertedContact->id ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $createdDate,
];
$this->restoreAnyTrashedEntity($this->config->leads(), $crmData['Id']);
/** @var Lead */
$lead = $this->config->leads()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);
if ($lead->wasChanged('converted_at') && $lead->getConvertedAt() !== null) {
event(new LeadConverted($lead));
}
$this->handleObjectDeletion($lead, $crmData);
return $lead;
}
/**
* @inheritdoc
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
$fields = $this->getAllFieldsAsArray('account');
if (\in_array('Id', $fields, true) === false) {
return $syncCount;
}
$query = '
SELECT ' . rtrim(implode(',', $fields), ',') . '
FROM Account
WHERE LastModifiedDate > :since
ORDER BY LastModifiedDate ASC';
try {
$sfAccounts = $this->queryHandler->query($query, [
'since' => $since->format('Y-m-d\TH:i:s\Z'),
]);
foreach ($sfAccounts as $sfAccount) {
// Only sync if previously imported.
if ($this->hasAccount($sfAccount['Id'])) {
$this->importAccount($sfAccount);
$syncCount++;
}
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
}
$this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::ACCOUNT);
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
$fields = $this->getAllFieldsAsArray('account');
if (! in_array('Id', $fields, true)) {
$this->logger->info('[Salesforce] Sync account cancelled. Fields are not available.', [
'crmId' => $crmId,
'userId' => $this->profile->getUserId(),
]);
return null;
}
$sfAccount = $this->getRecord('Account', $crmId, $fields);
return $this->importAccount($sfAccount);
}
private function importAccount($crmData): Account
{
$countryCode = $crmData['BillingCountryCode'] ?? $crmData['ShippingCountryCode'] ?? null;
// Salesforce allows custom "countries" to be created. Disregard these.
if ($countryCode && $this->countriesMap->countryExists($countryCode) === false) {
$countryCode = null;
}
// If we have no country code, try to parse it from the country names.
if ($countryCode === null && empty($crmData['BillingCountry']) === false) {
$countryCode = $this->convertCountryNameToCode($crmData['BillingCountry']);
}
if ($countryCode === null && empty($crmData['ShippingCountry']) === false) {
$countryCode = $this->convertCountryNameToCode($crmData['ShippingCountry']);
}
if (empty($crmData['Phone']) === false) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($crmData['Phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$industry = null;
if (empty($crmData['Industry']) === false) {
$industry = mb_strimwidth($crmData['Industry'], 0, 40);
}
$domain = null;
if (empty($crmData['Website']) === false) {
$domain = mb_strimwidth($crmData['Website'], 0, 191);
$domain = StringUtil::resolveDomain($domain);
}
$createdDate = null;
if (empty($crmData['CreatedDate']) === false) {
$createdDate = Carbon::parse($crmData['CreatedDate'])->setTimezone('UTC');
}
$profile = $this->getOwnerProfile($crmData['OwnerId'] ?? null);
$data = [
'team_id' => $this->team->id,
'user_id' => $profile?->user_id,
'owner_id' => $crmData['OwnerId'],
'name' => mb_strimwidth($crmData['Name'], 0, 191),
'photo_path' => $this->prospectPhotoPathService->getOrGeneratePhotoPath(
crmConfiguration: $this->config,
crmProviderId: $crmData['Id'],
modelType: Account::class,
fileName: $crmData['Id'],
avatarText: $crmData['Name']
),
'industry' => $industry,
'domain' => $domain,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $createdDate,
];
$this->restoreAnyTrashedEntity($this->config->accounts(), $crmData['Id']);
/** @var Account */
$account = $this->config->accounts()->updateOrCreate(['crm_provider_id' => $crmData['Id']], $data);
$this->handleObjectDeletion($account, $crmData);
return $account;
}
/**
* @inheritdoc
*/
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$syncCount = 0;
$logParams = $parameters;
$parameters['profile'] = $this->profile;
$logParams['user'] = $this->profile->getUserId();
if (count($strategies) > 1) {
$this->logger->warning('[' . $this->getDisplayName() . '] Multiple sync strategies used', [
'teamId' => $this->team->getUuid(),
'params' => $logParams,
'strategies_count' => count($strategies),
]);
}
foreach ($strategies as $syncStrategy) {
$name = $syncStrategy->getStrategyName();
try {
$sfOpportunities = $syncStrategy->fetchOpportunities($parameters);
$totalRecords = $sfOpportunities->count();
foreach ($sfOpportunities as $sfOpportunity) {
$this->importOpportunity($sfOpportunity);
$syncCount++;
}
} catch (NoResultsException $noResultsException) {
// Nothing to sync.
$this->logger->warning('[' . $this->getDisplayName() . '] No opportunities found', [
'teamId' => $this->team->getUuid(),
'name' => $name,
'params' => $logParams,
'reason' => $noResultsException->getMessage(),
]);
} catch (CrmException $crmException) {
// Nothing to sync.
$this->logger->warning('[' . $this->getDisplayName() . '] Opportunity sync failed', [
'teamId' => $this->team->getUuid(),
'name' => $name,
'params' => $logParams,
'reason' => $crmException->getMessage(),
]);
}
}
$this->syncRemotelyDeletedObjectsWithErrorHandling(CrmObject::OPPORTUNITY, ['params' => $logParams]);
// debug to see how if count of opportunities reaches 1000
if ($syncCount >= 1000) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Sync Opportunities - count warning',
[
'team_id' => $this->team->getId(),
'params' => $logParams,
'count' => $syncCount,
'strategies_count' => count($strategies),
'total_records' => $totalRecords ?? null,
]
);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY
);
$parameters = [
'profile' => $this->profile,
'crm_id' => $crmId,
];
try {
$sfOpportunity = $strategy->fetchOpportunities($parameters);
} catch (HttpNotFoundException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->id_string,
'crmId' => $crmId,
]);
return null;
} catch (CrmException $crmException) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity sync failed', [
'teamId' => $this->team->id_string,
'crmId' => $crmId,
'exception' => $crmException->getMessage(),
]);
return null;
}
if ($sfOpportunity instanceof ArrayIterator) {
return $this->importOpportunity($sfOpportunity->getItems());
}
return $this->importOpportunity($sfOpportunity);
}
private function importOpportunity($crmData): ?Opportunity
{
/** @var ?Stage $stage */
$stage = null;
if (isset($crmData['StageName'])) {
$stage = $this->config
->stages()
->where('name', $crmData['StageName'])
->where('type', Stage::TYPE_OPPORTUNITY)
->orderBy('is_selectable', 'DESC')
->orderBy('id')
->first();
if ($stage === null) {
// Import it.
$stage = $this->importStages([Stage::TYPE_OPPORTUNITY], $crmData['StageName']);
}
}
$recordType = null;
if (empty($crmData['RecordTypeId']) === false) {
/** @var ?RecordType $recordType */
$recordType = $this->config->recordTypes()
->where('crm_provider_id', $crmData['RecordTypeId'])
...
|
63048
|
|
51523
|
1115
|
9
|
2026-04-20T06:09:39.441319+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776665379441_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-20692-fix Workspace associated with branch 'JY-20692-fix-integration-app-[API_KEY]' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out JY-20692-fix-integration-app-[API_KEY]
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
#11986 on JY-20692-fix-integration-app-toke…hange, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-20692-fix-integration-app-token-auth-response-change' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.90263367,"width":0.11037234,"height":0.040702313},"value":"Workspace associated with branch 'JY-20692-fix-integration-app-token-auth-response-change' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.90263367,"width":0.102726065,"height":0.040702313},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"bounds":{"left":0.8753325,"top":0.9489226,"width":0.017287234,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"bounds":{"left":0.89793885,"top":0.9489226,"width":0.023603724,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.016289894,"height":0.0},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Checked out JY-20692-fix-integration-app-token-auth-response-change","depth":3,"bounds":{"left":0.016954787,"top":0.8595371,"width":0.16223404,"height":0.016759777},"value":"Checked out JY-20692-fix-integration-app-token-auth-response-change","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.016954787,"top":0.8611333,"width":0.027260639,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.045877658,"top":0.8611333,"width":0.1349734,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11986 on JY-20692-fix-integration-app-toke…hange, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12333777,"height":0.025538707},"help_text":"Pull request #11986 exists for current branch JY-20692-fix-integration-app-toke…hange","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8171542,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.8324468,"top":0.019952115,"width":0.0831117,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8955017672122641730
|
-7453102674190119738
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-20692-fix Workspace associated with branch 'JY-20692-fix-integration-app-[API_KEY]' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out JY-20692-fix-integration-app-[API_KEY]
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
#11986 on JY-20692-fix-integration-app-toke…hange, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions...
|
NULL
|
|
51526
|
1115
|
10
|
2026-04-20T06:09:44.689285+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776665384689_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-20692-fix Workspace associated with branch 'JY-20692-fix-integration-app-[API_KEY]' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11986 on JY-20692-fix-integration-app-toke…hange, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
34
1
34
62
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u ...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-20692-fix-integration-app-token-auth-response-change' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.90263367,"width":0.11037234,"height":0.040702313},"value":"Workspace associated with branch 'JY-20692-fix-integration-app-token-auth-response-change' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.90263367,"width":0.102726065,"height":0.040702313},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"bounds":{"left":0.8753325,"top":0.9489226,"width":0.017287234,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"bounds":{"left":0.89793885,"top":0.9489226,"width":0.023603724,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.016289894,"height":0.0},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11986 on JY-20692-fix-integration-app-toke…hange, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12333777,"height":0.025538707},"help_text":"Pull request #11986 exists for current branch JY-20692-fix-integration-app-toke…hange","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8171542,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.8324468,"top":0.019952115,"width":0.0831117,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"33","depth":4,"bounds":{"left":0.35073137,"top":0.15003991,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.36303192,"top":0.15003991,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"19","depth":4,"bounds":{"left":0.37300533,"top":0.15003991,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.38430852,"top":0.14844373,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.39162233,"top":0.14844373,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse HubSpot\\Client\\Crm\\Deals\\Model\\CollectionResponseAssociatedId;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Models\\Account;\nuse Exception;\nuse Jiminny\\Component\\DealInsights\\Forecast\\Forecast;\nuse Jiminny\\Jobs\\Crm\\MatchActivitiesToNewOpportunity;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Crm\\BusinessProcess;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Models\\Opportunity;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\Stage;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Crm\\Hubspot\\DealFieldsService;\nuse Jiminny\\Services\\Crm\\Hubspot\\OpportunitySyncStrategy\\HubspotSingleSyncStrategy;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Services\\Crm\\OpportunitySyncStrategyResolver;\nuse Jiminny\\Utils\\CurrencyFormatter;\n\n/**\n * Optimized sync methods for better performance\n * These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains\n */\ntrait OpportunitySyncTrait\n{\n private const int BATCH_SIZE = 100;\n private const int BATCH_PROCESS_SIZE = 800;\n\n protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;\n protected CrmEntityRepository $crmEntityRepository;\n protected DealFieldsService $dealFieldsService;\n\n private ?array $cachedClosedDealStages = null;\n private array $cachedBusinessProcesses = [];\n private array $cachedStages = [];\n\n public function syncOpportunities(array $parameters, ?string $strategy = null): int\n {\n $strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);\n $parameters['config'] = $this->config;\n $syncCount = 0;\n $reportedTotal = 0;\n $lastSyncedId = [];\n\n try {\n foreach ($strategies as $strategyName => $syncStrategy) {\n $this->logger->info(\n '[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .\n $strategyName\n );\n\n $total = 0;\n $lastId = null;\n $buffer = [];\n\n // HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies\n foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {\n $buffer[] = $hsOpportunity;\n\n // process every 800 rows (fits < 1 000 association limit)\n if (\\count($buffer) >= self::BATCH_PROCESS_SIZE) {\n $syncCount += $this->processOpportunityBatch($buffer);\n $buffer = [];\n }\n }\n\n // leftovers\n if ($buffer) {\n $syncCount += $this->processOpportunityBatch($buffer);\n }\n\n $reportedTotal += $total;\n $lastSyncedId = $lastId;\n }\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException | CrmException $e) {\n $this->handleSyncException($e, $parameters);\n }\n\n $this->logger->info(\n '[HubSpot] Synced opportunities',\n [\n 'team' => $this->team->getId(),\n 'sync_count' => $syncCount,\n 'total' => $reportedTotal,\n 'last_synced_id' => $lastSyncedId,\n ]\n );\n\n return $reportedTotal;\n }\n\n private function handleSyncException(\\Throwable $e, array $parameters): void\n {\n if (($parameters['since'] ?? null) instanceof Carbon) {\n $parameters['since'] = $parameters['since']->toDateTimeString();\n }\n $parameters['config'] = $this->config->getId();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [\n 'teamId' => $this->team->getUuid(),\n 'parameters' => $parameters,\n 'reason' => $e->getMessage(),\n ]);\n }\n\n /**\n * @inheritdoc\n */\n public function syncOpportunity(string $crmId): ?Opportunity\n {\n $strategy = $this->opportunitySyncStrategyResolver->resolve(\n $this->config,\n OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,\n );\n\n $parameters = [\n 'config' => $this->config,\n 'crm_id' => $crmId,\n ];\n\n try {\n if (! $strategy instanceof HubspotSingleSyncStrategy) {\n throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');\n }\n\n $hsOpportunity = $strategy->fetchOpportunity($parameters);\n } catch (\\HubSpot\\Client\\Crm\\Deals\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n $hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);\n\n return $this->importOrUpdateOpportunity($hsOpportunity);\n }\n\n /**\n * Process webhook-collected opportunity batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportOpportunityBatch jobs for batch processing.\n *\n * @return int Number of opportunity IDs dispatched to jobs\n */\n public function batchSyncOpportunities(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,\n $configId\n );\n }\n\n /**\n * Import a batch of opportunities by their CRM IDs.\n * Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().\n *\n * @param array<string> $crmIds HubSpot deal CRM IDs\n *\n * @return array{success: array, failed_ids: array, errors?: array<string, string>}\n */\n public function importOpportunityBatchByIds(array $crmIds): array\n {\n $fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);\n\n $allDeals = [];\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n $deals = $this->client->getOpportunitiesByIds($chunk, $fields);\n foreach ($deals as $deal) {\n $allDeals[] = $deal;\n }\n }\n\n // IDs not returned by HubSpot are likely deleted or inaccessible deals.\n // These are not failures — retrying won't bring them back.\n $fetchedIds = array_map('strval', array_column($allDeals, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allDeals),\n ]);\n }\n\n if (empty($allDeals)) {\n return ['success' => [], 'failed_ids' => []];\n }\n\n return $this->importOpportunityBatch($allDeals);\n }\n\n private function getClosedDealStages(): array\n {\n if ($this->cachedClosedDealStages !== null) {\n return $this->cachedClosedDealStages;\n }\n\n $stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);\n $data = [\n 'lost' => [],\n 'won' => [],\n ];\n\n foreach ($stages as $stage) {\n if ($stage->probability == 0.00) {\n $data['lost'][] = $stage->crm_provider_id;\n }\n if ($stage->probability == 100.00) {\n $data['won'][] = $stage->crm_provider_id;\n }\n }\n\n $this->cachedClosedDealStages = $data;\n\n return $data;\n }\n\n /**\n * Import deals into the database with pre-fetched associations.\n *\n * API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT\n * caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()\n * where Laravel retries the whole job with backoff. After all retries exhausted,\n * failed() requeues all IDs to Redis.\n *\n * The per-deal loop catches exceptions individually. A deal can end up in three states:\n * - success: imported/updated successfully\n * - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)\n * These are permanent issues — retrying won't fix them.\n * - skipped (null): missing dependencies (no account, unknown pipeline/stage).\n * This is acceptable — the deal cannot be imported until those exist.\n */\n private function importOpportunityBatch(array $deals): array\n {\n $syncedOpportunities = [\n 'success' => [],\n 'failed_ids' => [],\n ];\n $dealIds = array_column($deals, 'id');\n\n // Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the\n // queue job retries the whole batch and eventually requeues all deal IDs back to Redis.\n try {\n $companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');\n $contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');\n\n $associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);\n\n $existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(\n $this->config,\n array_map('strval', $dealIds)\n );\n $existingCrmIdSet = array_flip($existingCrmIds);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [\n 'teamId' => $this->team->getId(),\n 'dealCount' => count($dealIds),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n\n foreach ($deals as $deal) {\n try {\n $deal['associations'] = $this->prepareAssociationsForOpportunity(\n $deal['id'],\n $companyAssociations,\n $contactAssociations,\n $associationsData\n );\n\n $syncedOpportunity = $this->importOrUpdateOpportunity(\n $deal,\n isset($existingCrmIdSet[(string) $deal['id']])\n );\n if ($syncedOpportunity) {\n $syncedOpportunities['success'][] = $syncedOpportunity;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $deal['id'],\n 'error' => $e->getMessage(),\n ]);\n $syncedOpportunities['failed_ids'][] = $deal['id'];\n $syncedOpportunities['errors'][$deal['id']] = $e->getMessage();\n }\n }\n\n return $syncedOpportunities;\n }\n\n /**\n * Prepare associated entities for opportunities with optimized batch processing\n * Returns structured data with CRM ID to DB ID mappings for each opportunity\n */\n private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array\n {\n // Step 1: Collect all unique company and contact IDs from associations\n $allCompanyIds = $this->flattenAssociationIds($companyAssociations);\n $allContactIds = $this->flattenAssociationIds($contactAssociations);\n\n // Step 2: Batch sync missing entities and get CRM ID to DB ID mappings\n $companyIdMappings = [];\n $contactIdMappings = [];\n\n if (! empty($allCompanyIds)) {\n $companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);\n }\n\n if (! empty($allContactIds)) {\n $contactIdMappings = $this->prepareAssociatedContacts($allContactIds);\n }\n\n return [\n 'company_id_mappings' => $companyIdMappings,\n 'contact_id_mappings' => $contactIdMappings,\n ];\n }\n\n /**\n * Flatten association data to get unique IDs\n */\n private function flattenAssociationIds(array $associations): array\n {\n $ids = [];\n foreach ($associations as $dealAssociations) {\n if (is_array($dealAssociations)) {\n foreach ($dealAssociations as $id) {\n $ids[$id] = true;\n }\n }\n }\n\n return array_keys($ids);\n }\n\n /**\n * Batch sync missing accounts\n */\n private function prepareAssociatedAccounts(array $companyIds): array\n {\n // Find which accounts already exist\n $existingAccounts = $this->crmEntityRepository\n ->findAccountsByExternalIds($this->config, $companyIds);\n\n $existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();\n\n $existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {\n return [$account->getCrmProviderId() => $account->getId()];\n })->toArray();\n\n $missingCompanyIds = array_diff($companyIds, $existingCompanyIds);\n\n if (empty($missingCompanyIds)) {\n return $existingAccountsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [\n 'teamId' => $this->team->getUuid(),\n 'total_companies' => count($companyIds),\n 'existing_companies' => count($existingCompanyIds),\n 'missing_companies' => count($missingCompanyIds),\n ]);\n\n // we already have limit on opportunity ids count\n // Initialize variable before try block\n $syncedAccountsData = [];\n\n try {\n $syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [\n 'size' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedAccountsData = [];\n }\n\n return $existingAccountsData + $syncedAccountsData;\n }\n\n /**\n * Prepare associated contacts - find existing and sync missing ones\n * Returns mapping of CRM ID to DB ID\n */\n private function prepareAssociatedContacts(array $contactIds): array\n {\n // Find which contacts already exist\n $existingContacts = $this->crmEntityRepository\n ->findContactsByExternalIds($this->config, $contactIds);\n\n $existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();\n\n // Create mapping for existing contacts\n $existingContactsData = $existingContacts->mapWithKeys(function ($contact) {\n return [$contact->getCrmProviderId() => $contact->getId()];\n })->toArray();\n\n $missingContactIds = array_diff($contactIds, $existingContactIds);\n\n if (empty($missingContactIds)) {\n return $existingContactsData;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [\n 'teamId' => $this->team->getUuid(),\n 'total_contacts' => count($contactIds),\n 'existing_contacts' => count($existingContactIds),\n 'missing_contacts' => count($missingContactIds),\n ]);\n\n // Sync missing contacts using batch API\n try {\n $syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [\n 'size' => count($missingContactIds),\n 'error' => $e->getMessage(),\n ]);\n $syncedContactsData = [];\n }\n\n return $existingContactsData + $syncedContactsData;\n }\n\n private function batchSyncCrmObjects(string $objectType, array $crmIds): array\n {\n $syncObjects = [];\n $crmObjectIds = array_values($crmIds);\n\n foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {\n try {\n $objects = $objectType === 'companies' ?\n $this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :\n $this->client->getContactsByIds($chunk, $this->getContactFields());\n\n foreach ($objects as $objectId => $objectData) {\n $this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [\n 'requested_count' => count($chunk),\n 'synced_count' => count($objects),\n ]);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [\n 'ids' => $chunk,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncObjects;\n }\n\n private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void\n {\n try {\n $object = $objectType === 'companies' ?\n $this->importAccount($objectData) :\n $this->importContact($objectData);\n\n if ($object) {\n $syncObjects[$object->getCrmProviderId()] = $object->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [\n 'id' => $objectId,\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n /**\n * Prepare associations for a single opportunity\n *\n * The return value is an array with the following structure:\n * [\n * 'companies' => [\n * $companyCrmId => $companyId,\n * ...\n * ],\n * 'contacts' => [\n * $contactCrmId => $contactId,\n * ...\n * ],\n * 'account_id' => $accountId,\n * ]\n */\n private function prepareAssociationsForOpportunity(\n string $oppCrmId,\n array $companyAssociations,\n array $contactAssociations,\n array $associationsData\n ): array {\n $associations = [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n\n $oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];\n foreach ($oppCompanyIds as $companyCrmId) {\n if (isset($associationsData['company_id_mappings'][$companyCrmId])) {\n $associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];\n\n // Set primary account (first company becomes primary account)\n if ($associations['account_id'] === null) {\n $associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];\n }\n }\n }\n\n $oppContactIds = $contactAssociations[$oppCrmId] ?? [];\n foreach ($oppContactIds as $contactCrmId) {\n if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {\n $associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];\n }\n }\n\n return $associations;\n }\n\n /**\n * Update only associations for an opportunity\n */\n private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void\n {\n // Update contact associations\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n // Update company (account) associations\n $this->updateOpportunityAccount($opportunity, $associations['account_id']);\n }\n\n /**\n * Remove all contact associations from an opportunity\n */\n private function removeAllOpportunityContacts(Opportunity $opportunity): void\n {\n $currentCount = (int) $opportunity->contacts()->count();\n\n if ($currentCount > 0) {\n $opportunity->contacts()->detach();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_count' => $currentCount,\n ]);\n }\n }\n\n private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void\n {\n if ($accountId === null) {\n // No account ID provided - keep current account\n return;\n }\n\n $currentAccountId = $opportunity->getAccountId();\n\n // Only update if account has changed\n if ($currentAccountId !== $accountId) {\n $opportunity->account_id = $accountId;\n $opportunity->save();\n\n $this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [\n 'opportunity_id' => $opportunity->getId(),\n 'old_account_id' => $currentAccountId,\n 'new_account_id' => $accountId,\n ]);\n }\n }\n\n /**\n * Find existing opportunities by external IDs (OPTIMIZED VERSION)\n * Uses batch query for better performance\n */\n private function findExistingOpportunities(array $crmIds): Collection\n {\n return $this->crmEntityRepository\n ->findOpportunitiesByExternalIds($this->config, $crmIds);\n }\n\n private function processOpportunityBatch(array $opportunities): int\n {\n $syncedOpportunities = $this->importOpportunityBatch($opportunities);\n\n return count($syncedOpportunities['success'] ?? []);\n }\n\n /**\n * Convert single deal associations from HubSpot format to internal format\n * Handles both HubSpot SDK objects and array formats\n *\n * @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed\n *\n * @return array Processed associations with DB IDs\n */\n private function convertDealAssociations(array $opportunityAssociations): array\n {\n $associations = $this->initializeAssociationsStructure();\n\n if (empty($opportunityAssociations)) {\n return $associations;\n }\n\n $associationIds = $this->extractAssociationIds($opportunityAssociations);\n\n $this->processCompanyAssociations($associationIds, $associations);\n $this->processContactAssociations($associationIds, $associations);\n\n return $associations;\n }\n\n private function initializeAssociationsStructure(): array\n {\n return [\n 'companies' => [],\n 'contacts' => [],\n 'account_id' => null, // Primary account for opportunity\n ];\n }\n\n private function extractAssociationIds(array $opportunityAssociations): array\n {\n $associationIds = [];\n\n foreach ($opportunityAssociations as $type => $associationData) {\n if (! empty($associationData)) {\n $associationIds[$type] = $this->convertSingleDealAssociations($associationData);\n }\n }\n\n return $associationIds;\n }\n\n private function processCompanyAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['companies'])) {\n return;\n }\n\n $companyId = $associationIds['companies'][0];\n $account = $this->findOrSyncAccount($companyId);\n\n if ($account instanceof Account) {\n $associations['companies'][$companyId] = $account->getId();\n $associations['account_id'] = $account->getId();\n }\n }\n\n private function processContactAssociations(array $associationIds, array &$associations): void\n {\n if (empty($associationIds['contacts'])) {\n return;\n }\n\n foreach ($associationIds['contacts'] as $contactId) {\n $contact = $this->findOrSyncContact($contactId);\n\n if ($contact instanceof Contact) {\n $associations['contacts'][$contactId] = $contact->getId();\n }\n }\n }\n\n private function findOrSyncAccount(string $companyId): ?Account\n {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);\n\n if (! $account instanceof Account) {\n $account = $this->syncAccount($companyId);\n }\n\n return $account;\n }\n\n private function findOrSyncContact(string $contactId): ?Contact\n {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);\n\n if (! $contact instanceof Contact) {\n $contact = $this->syncContact($contactId);\n }\n\n return $contact;\n }\n\n private function convertSingleDealAssociations($opportunityAssociations = null): array\n {\n $associationData = [];\n\n if ($opportunityAssociations === null) {\n return $associationData;\n }\n\n // Handle array input (from extractAssociationIds)\n if (is_array($opportunityAssociations)) {\n return $opportunityAssociations;\n }\n\n // Handle CollectionResponseAssociatedId object\n if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {\n foreach ($opportunityAssociations->getResults() as $association) {\n $associationData[] = $association->getId();\n }\n }\n\n return $associationData;\n }\n\n private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity\n {\n if (empty($crmData['properties'])) {\n return null;\n }\n\n $crmId = (string) $crmData['id'];\n $properties = $crmData['properties'];\n $associations = $crmData['associations'] ?? [];\n\n $opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(\n $this->config,\n $crmId\n );\n\n if ($opportunityExists) {\n return $this->updateOpportunity($crmId, $properties, $associations);\n } else {\n return $this->createOpportunity($crmId, $properties, $associations);\n }\n }\n\n /**\n * Create new opportunity\n */\n private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n if (! $accountId) {\n return null;\n }\n\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n if (! $businessProcess) {\n return null;\n }\n\n $stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);\n if (! $stage) {\n return null;\n }\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->importOpportunityContacts($opportunity, $associations['contacts']);\n\n if ($opportunity->wasRecentlyCreated) {\n MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());\n }\n\n return $opportunity;\n }\n\n /**\n * Update existing opportunity\n */\n private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity\n {\n $accountId = $this->resolveAccountId($associations);\n $businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);\n $stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;\n\n $data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);\n\n $attributes = [\n 'crm_configuration_id' => $this->config->getId(),\n 'crm_provider_id' => $crmId,\n ];\n\n $values = array_merge($attributes, $data);\n $opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);\n\n $this->importExternalFieldData($properties, $opportunity->getId());\n $this->updateOpportunityAssociations($opportunity, $associations);\n\n return $opportunity;\n }\n\n private function resolveAccountId(array $associations): ?int\n {\n if (! empty($associations['accountId'])) {\n return $associations['accountId'];\n }\n\n if (empty($associations)) {\n return null;\n }\n\n // we can't resolve multiple account ids (currently SDK returns one company)\n foreach ($associations['companies'] as $accountId) {\n return $accountId;\n }\n\n return null;\n }\n\n private function buildOpportunityData(\n array $properties,\n ?int $accountId,\n ?BusinessProcess $businessProcess,\n ?Stage $stage\n ): array {\n $ownerId = null;\n $profile = null;\n if (! empty($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $name = 'Unknown';\n if (isset($properties['dealname'])) {\n $name = mb_strimwidth($properties['dealname'], 0, 128);\n }\n\n $amount = $this->resolveAmount($properties);\n $currency = $properties['deal_currency_code'] ?? null;\n\n $closeDate = null;\n if (! empty($properties['closedate'])) {\n $closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');\n }\n\n $remotelyCreatedAt = null;\n if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {\n $date = $this->parseCleanDatetime($properties['createdate']);\n $remotelyCreatedAt = $date?->format('Y-m-d H:i:s');\n }\n\n $closedStages = $this->getClosedDealStages();\n $isWon = in_array($properties['dealstage'], $closedStages['won']);\n $isLost = in_array($properties['dealstage'], $closedStages['lost']);\n\n $data = [\n 'team_id' => $this->team->getId(),\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'value' => ! empty($amount) ? $amount : null,\n 'currency_code' => CurrencyFormatter::formatCode($currency),\n 'close_date' => $closeDate,\n 'is_closed' => $isWon || $isLost,\n 'is_won' => $isWon,\n 'remotely_created_at' => $remotelyCreatedAt,\n 'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),\n 'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),\n ];\n\n if ($accountId) {\n $data['account_id'] = $accountId;\n }\n\n if ($stage) {\n $data['stage_id'] = $stage->id;\n }\n\n if ($businessProcess) {\n $recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);\n if ($recordType) {\n $data['record_type_id'] = $recordType->id;\n }\n }\n\n return $data;\n }\n\n private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess\n {\n if ($pipelineId === null) {\n return null;\n }\n\n if (isset($this->cachedBusinessProcesses[$pipelineId])) {\n return $this->cachedBusinessProcesses[$pipelineId];\n }\n\n $businessProcess = $this->getBusinessProcess($pipelineId);\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->importStages();\n $businessProcess = $this->getBusinessProcess($pipelineId);\n }\n\n if (! $businessProcess instanceof BusinessProcess) {\n $this->logger->info(\n '[HubSpot] Deal is not attached to a pipeline',\n [\n 'pipeline' => $pipelineId]\n );\n }\n\n $this->cachedBusinessProcesses[$pipelineId] = $businessProcess;\n\n return $businessProcess;\n }\n\n private function getBusinessProcess(string $pipelineId): ?BusinessProcess\n {\n return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);\n }\n\n private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage\n {\n if (empty($stageId)) {\n return null;\n }\n\n $cacheKey = $businessProcess->getId() . ':' . $stageId;\n if (isset($this->cachedStages[$cacheKey])) {\n return $this->cachedStages[$cacheKey];\n }\n\n $stage = $this->crmEntityRepository->getPipelineStageByConditions(\n $businessProcess,\n [\n 'crm_provider_id' => $stageId,\n 'type' => Stage::TYPE_OPPORTUNITY,\n ]\n );\n\n if ($stage === null) {\n $this->importStages(null, $stageId);\n }\n\n if ($stage === null) {\n $this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);\n }\n\n $this->cachedStages[$cacheKey] = $stage;\n\n return $stage;\n }\n\n private function resolveAmount(array $properties): ?string\n {\n $amount = null;\n if (! empty($properties['amount'])) {\n $amount = str_replace(',', '', $properties['amount']);\n }\n\n if ($this->config->hasDefaultCurrencyFieldSet()) {\n $valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();\n $amount = $properties[$valueFieldName] ?? $amount;\n }\n\n return $amount;\n }\n\n private function parseCleanDatetime(string $datetime): ?Carbon\n {\n // Treat pre-1980 values as invalid\n $minValidDate = Carbon::parse('1980-01-01 00:00:00');\n\n try {\n $date = Carbon::parse($datetime);\n\n if ($minValidDate->gt($date)) {\n return null;\n }\n\n return $date;\n } catch (Exception) {\n return null; // On parse error, treat as null\n }\n }\n\n private function resolveDealProbability(?string $stageProbability): int\n {\n if ($stageProbability === null) {\n return 0;\n }\n\n $probability = (float) $stageProbability;\n\n return $probability > 1 ? 0 : (int) ($probability * 100);\n }\n\n private function resolveForecastCategory(?string $forecastCategory): string\n {\n if (! $forecastCategory) {\n return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;\n }\n\n $forecastCategory = str_replace('_', ' ', $forecastCategory);\n\n return ucwords(strtolower($forecastCategory));\n }\n\n private function importExternalFieldData(array $properties, int $opportunityId): void\n {\n $crmFields = $this->getOpportunitySyncableFields();\n $this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);\n }\n\n private function importOpportunityContacts(Opportunity $opportunity, array $associations): void\n {\n // Handle empty or missing contact associations\n if (empty($associations)) {\n // Remove all existing contact associations if none provided\n $this->removeAllOpportunityContacts($opportunity);\n\n return;\n }\n\n // Use differential sync approach for better performance and accuracy\n $this->syncOpportunityContactsDifferential($opportunity, $associations);\n }\n\n /**\n * Sync opportunity contacts using differential approach\n * This compares current vs new associations and only makes necessary changes\n */\n private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void\n {\n $currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);\n $contactAssociationIds = array_keys($contactAssociations);\n\n $contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);\n $contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);\n\n if (empty($contactsToAdd) && empty($contactsToRemove)) {\n return;\n }\n\n $this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);\n\n $this->removeContactAssociations($opportunity, $contactsToRemove);\n $this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);\n }\n\n private function getCurrentContactCrmIds(Opportunity $opportunity): array\n {\n return $opportunity->contacts()\n ->pluck('contacts.crm_provider_id')\n ->toArray();\n }\n\n private function logContactAssociationChanges(\n Opportunity $opportunity,\n array $currentContactCrmIds,\n array $contactAssociations,\n array $contactsToAdd,\n array $contactsToRemove\n ): void {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [\n 'opportunity_id' => $opportunity->getId(),\n 'current_contacts' => $currentContactCrmIds,\n 'new_contacts' => $contactAssociations,\n 'contacts_to_add' => $contactsToAdd,\n 'contacts_to_remove' => $contactsToRemove,\n ]);\n }\n\n private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void\n {\n if (empty($contactsToRemove)) {\n return;\n }\n\n $contactsToDetach = $opportunity->contacts()\n ->whereIn('contacts.crm_provider_id', $contactsToRemove)\n ->pluck('contacts.id')\n ->toArray();\n\n if (! empty($contactsToDetach)) {\n $opportunity->contacts()->detach($contactsToDetach);\n\n $this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'removed_contact_crm_ids' => $contactsToRemove,\n 'removed_contact_count' => count($contactsToDetach),\n ]);\n }\n }\n\n private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void\n {\n if (empty($contactsToAdd)) {\n return;\n }\n\n $contactsAdded = [];\n foreach ($contactsToAdd as $crmId) {\n $id = $contactAssociations[$crmId];\n\n if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {\n $contactsAdded[] = $crmId;\n }\n }\n\n $this->logAddedContacts($opportunity, $contactsAdded);\n }\n\n private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);\n\n if (! $contact) {\n return false;\n }\n\n return $this->performContactAttachment($opportunity, $contact, $crmId);\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [\n 'opportunity_id' => $opportunity->getId(),\n 'contact_crm_id' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool\n {\n try {\n $opportunity->contacts()->attach($contact->getId(), [\n 'crm_provider_id' => $crmId,\n ]);\n\n return true;\n } catch (\\Illuminate\\Database\\QueryException $e) {\n if (str_contains($e->getMessage(), 'Duplicate entry')) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [\n 'contact_id' => $contact->getId(),\n 'contact_crm_id' => $crmId,\n 'opportunity_id' => $opportunity->getId(),\n ]);\n\n return false;\n }\n\n throw $e;\n }\n }\n\n private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void\n {\n if (! empty($contactsAdded)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [\n 'opportunity_id' => $opportunity->getId(),\n 'contacts_to_add_count' => count($contactsAdded),\n 'added_contact_crm_ids' => $contactsAdded,\n 'added_contacts_count' => count($contactsAdded),\n ]);\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.40026596,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.4089096,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.41988033,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.42852393,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.43716756,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.4481383,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.45910904,"top":0.09896249,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.48570478,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.49667552,"top":0.09896249,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.69913566,"top":0.09896249,"width":0.02825798,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"34","depth":4,"bounds":{"left":0.66855055,"top":0.123703115,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.68085104,"top":0.123703115,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"34","depth":4,"bounds":{"left":0.69015956,"top":0.123703115,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"62","depth":4,"bounds":{"left":0.7024601,"top":0.123703115,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7144282,"top":0.12210695,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.72174203,"top":0.12210695,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\n;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067ß - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';","depth":4,"value":"SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993\nSELECT * FROM users WHERE id = 25061;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 994;\nSELECT * FROM crm_profiles WHERE user_id = 25061;\n\nselect * from crm_configurations where id = 834;\nSELECT * FROM teams WHERE id = 882;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;\n\nSELECT * FROM contacts where crm_configuration_id = 834;\nSELECT * FROM opportunities WHERE team_id = 933\n# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');\nAND id IN (8482561,18352941,19042734,19232139,19445140,19472541);\nSELECT * FROM opportunity_contacts\nWHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; #\nSELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\nselect crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id\nwhere crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')\n# and l.converted_at IS NOT NULL\n;\n\n# ********************************************************************\nSELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')\nand opportunity_id IS NULL\norder by id desc;\n\nSELECT * FROM teams WHERE id = 604; # 598\nSELECT * FROM activities WHERE id = 74410828; # chelseaw@allvoices.co\nSELECT * FROM accounts WHERE id = 20068382;\nSELECT * FROM accounts WHERE id = 35186038;\n\nSELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 559 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;\nselect * from sidekick_settings where team_id = 781;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 711;\nSELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL\nand is_internal = 0 and status = 'completed'\norder by id desc;\n\nSELECT * FROM crm_layout_entities\nWHERE crm_layout_id IN (2352, 2353);\n;\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;\nSELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;\nselect * from contacts\nwhere crm_configuration_id = 530\nand crm_provider_id = 872252;\n\nselect * from activities where crm_configuration_id = 530\nand user_id = 14343 and type like '%softphone%'\nand created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';\n\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya\nSELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);\n\n\nSELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t\nJOIN crm_configurations c ON t.id = c.team_id\nWHERE t.status = 'active';\n\nSELECT * FROM teams where id = 1091;\nSELECT * FROM crm_configurations where team_id = 1091;\nSELECT * FROM activity_providers where team_id = 1091;\nSELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT * FROM teams WHERE name LIKE '%Leadventure%';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1091 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812\nSELECT * FROM teams where id = 862;\nSELECT * FROM crm_configurations where team_id = 862;\nSELECT * FROM activity_providers where team_id = 862;\nSELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\n\n\nSELECT t.id, crm.id, crm.provider, ap.* FROM teams t\njoin crm_configurations crm on t.id = crm.team_id\njoin activity_providers ap on t.id = ap.team_id\nwhere t.status = 'active' and ap.is_enabled = 1\nand crm.provider = 'hubspot'\nand ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',\n 'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');\n\nSELECT * FROM teams where id = 1068;\nSELECT * FROM crm_configurations where team_id = 1068;\nSELECT * FROM activity_providers where team_id = 1068;\n\nSELECT * FROM activities a\nwhere crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')\nand a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'\n )\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by a.id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1068 and sa.provider = 'hubspot';\n\n# ********************************************************************\n# ********************************************************************\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal , portalId: 6017093\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 933 and updated_at > '2026-02-06 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 933 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 834; # 882 - AnyVan , portalId: 5468262\nSELECT * FROM contacts WHERE crm_configuration_id = 834 and updated_at > '2026-03-30 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and updated_at > '2026-03-04 08:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 882 and sa.provider = 'hubspot';\nselect * from crm_layouts where crm_configuration_id = 834;\nselect * from crm_layout_entities where crm_layout_id = 2780;\nselect * from crm_fields where id IN (321153,321192,321193,321194);\n\nSELECT * FROM opportunities WHERE crm_configuration_id = 834 and id = 10993426;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 988; # 1057 - Teya (543ce4f4-168c-4571-91ea-5b35c253f06f) , portalId: 26651871\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1057 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1057 and sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations where id = 533; # 559 - Connectd , portalId: 6710988\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 559 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 801; # 852 - Rise Vision , portalId: 2700250\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 852 and updated_at > '2026-02-04 00:00:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 962; # 1034 - evergrowth.io , portalId: 143180990\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1034 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 1037; # 1102 - Jibble , portalId: 6649755\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1102 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 8\n\nSELECT * FROM crm_configurations where id = 1015; # 1049 - Travefy , portalId: 48904401\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1049 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 20\n\nSELECT * FROM crm_configurations where id = 64; # 70 - SalaryFinance , portalId: 3404115\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 70 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 6th last\n\nSELECT * FROM crm_configurations where id = 802; # 853 - Street Group , portalId: 7658438\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 853 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 10\n\nSELECT * FROM crm_configurations where id = 872; # 921 - In Professional Development , portalId: 9238273\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 921 and updated_at > '2026-02-04 12:30:00' order by updated_at desc; # 2\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 550; # 576 - SeedLegals , portalId: 3028661\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 576 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 989; # 1058 - rtaoutdoor.com , portalId: 22371204\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1058 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 896; # 946 - Mintago , portalId: 6621281\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 946 and updated_at > '2026-02-05 14:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 617; # 641 - PCS , portalId: 5244937\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 641 and updated_at > '2026-02-05 14:00:00' order by updated_at desc; # 7th\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 649; # 670 - Eventeny , portalId: 4492849\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-18 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 670 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; #\n\nSELECT * FROM crm_configurations where id = 48; # 51 - CleanCloud , portalId: 4373137\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-03-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 51 and updated_at > '2026-02-09 08:00:00' order by updated_at desc;\nselect * from users where team_id = 51; # 7783\nSELECT * FROM groups WHERE uuid_to_bin('8a8d2cb6-8b55-4fa3-8b5c-5f0e3d8de59a') = uuid; # 1130\nselect * from activity_searches where user_id = 7783;\nselect * from activity_search_filters where activity_search_id IN (32291, 32292);\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations where id = 272; # 290 - Bonham & Brook , portalId: 5705856\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-05 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 290 and updated_at > '2026-02-09 08:00:00' order by updated_at desc; # 6th\n# ********************************************************************\nSELECT * FROM crm_configurations where provider = 'hubspot';\nSELECT * FROM crm_configurations where id = 1056; # 1119 - Chromatic , portalId: 45602133\nSELECT * FROM opportunities WHERE team_id = 1119 and remotely_created_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 1119 and updated_at > '2026-02-09 09:00:00' order by updated_at desc; # null\n# ********************************************************************\n\nselect * from contacts where crm_provider_id = '003Uu00000ojD4NIAU';\nselect\n cp.*\n# DISTINCT t.id\n# cp.id, cp.user_id, t.id, cp.crm_configuration_id, cp.contact_fields\nFROM crm_profiles cp\nJOIN crm_configurations crm on crm.id = cp.crm_configuration_id\nJOIN users u on u.id = cp.user_id\nJOIN teams t ON t.id = crm.team_id\nWHERE crm.provider = 'salesforce' and t.status = 'active'\n and cp.archived_at IS NULL and u.deleted_at IS NULL\n and t.id NOT IN (1093)\n and t.id = 2\n and cp.contact_fields IS NULL;\n# and c.crm_provider_id = '003Uu00000ojD4NIAU';\n\nSELECT * FROM users WHERE id = 26484;\nSELECT * FROM crm_profiles WHERE user_id = 26484;\nSELECT * FROM social_accounts WHERE sociable_id = 26484;\nSELECT * FROM crm_configurations where provider = 'salesforce';\nselect * from users where id IN (10022, 10403);\nselect * from users where team_id IN (526);\nselect * from teams where id IN (526, 532);\nselect * from crm_configurations where id IN (500, 516);\nselect * from crm_profiles where crm_configuration_id IN (500, 516) and user_id IN (10022, 10403);\nselect * from contacts where crm_configuration_id IN (500, 516) and crm_provider_id = '003Uu00000ojD4NIAU';\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 526 and sa.provider = 'salesforce';\nselect * from team_settings where team_id IN (526, 532);\n\nselect * from users where id IN (22824);\nselect * from crm_profiles where crm_configuration_id IN (1026);\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1093 and sa.provider = 'salesforce';\n\nselect * from teams where id = 1099;\nselect * from users where id = 29643\n\nselect * from activity_processing_states;\n\nSELECT * FROM teams where name LIKE '%Fare%'; # 233\nSELECT * FROM opportunities where crm_configuration_id = 215\n# and crm_provider_id = 'oppo_ogESZf2P50nDrd1nGPvKDXeA6sSaTN5v51Lp4ayVzKR'\n;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1088 and sa.provider = 'hubspot';\n\nSELECT * FROM teams order by updated_at DESC\nSELECT * FROM crm_configurations WHERE id = 1019; # SimpleConsign 1088 - no social account\n\nselect * from crm_configurations where provider = 'pipedrive';\n\nselect * from teams where id = 957;\nselect * from crm_configurations where id = 957;\n\nSELECT * FROM teams WHERE name LIKE '%Prolific%'; # 544, 518, 10743\nSELECT * FROM opportunities where crm_configuration_id = 518 order by id desc;\n\nselect * from users where team_id = 1; # 26726 - Gabriela Dureva\nSELECT * FROM opportunities where user_id = 26726; # 16834447 - Prolific\nselect * from activities where user_id = 26726 order by id desc;\nselect * from contacts where crm_configuration_id = 1\nand email IN ('charlotte.ward@prolific.com', 'frankie.bryant@prolific.com'); # 2094416, 2093620\nSELECT * FROM contacts WHERE id = 6284931;\n\nSELECT p.* FROM activities a JOIN participants p ON a.id = p.activity_id\nWHERE a.user_id = 26726 and p.lead_id IN (2094416, 2093620) and a.created_at > '2026-01-01 00:00:00' order by p.email;\n\nselect * from activities where id IN (75509259,75509261,75509261,75511034,75026464,75517602,75517605);\nselect * from crm_configurations where id = 1;\n\n43801692-1aeb-32ce-acba-5b80a479701a\n44c3c9cf-6f5e-75f3-8179-bc9f75dd2b1b\n405975c0-b3d0-7aaa-821f-09d59cae6dd1\n4caf848d-4bed-2299-b248-7788d41f9fca\n49bedc3f-f196-eef3-89c3-dea6a3b4aa63\n43420989-a09d-b8f8-9806-c8bbf7a02aac\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nSELECT * FROM activities WHERE id = 75461988;\n\nSELECT * FROM activities WHERE uuid_to_bin('d6c5052e-e972-49e9-8912-26f2f7d6c5f6') = uuid;\n\nselect * from contacts where id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nselect * from users where id = 21047;\nSELECT * FROM crm_configurations WHERE id = 892;\nSELECT * FROM teams WHERE id = 942;\nselect * from opportunities where team_id = 942 order by updated_at desc;\nselect * from contacts where team_id = 942 order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 942 and sa.provider = 'hubspot';\n\nSELECT * FROM opportunities where team_id = 1 and crm_provider_id IN ('006Pq00000NeH6XIAV', '006Pq000007z8kdIAA'); # 10697889, 6621430\nSELECT * FROM crm_configurations WHERE id = 1;\nSELECT * FROM teams WHERE crm_id = 1;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1 and sa.provider = 'salesforce';\n\nselect id, user_id, opportunity_fields from crm_profiles where crm_configuration_id = 1\nSELECT * FROM opportunities where team_id = 1 order by updated_at desc; # 10697889, 6621430\n\nselect * from teams where id = 852;\nselect * from groups where id = 2286;\nselect * from sidekick_settings where team_id = 852;\nselect * from default_activity_types where team_id = 852;\n\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id AND p.id IS NULL -- no profile\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active' -- team is active\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1 AND u.deleted_at IS NULL\nAND u.crm_required = 1\nAND u.team_id = 1\nORDER BY u.team_id;\n\nSELECT * FROM crm_profiles cp where cp.crm_configuration_id = 1 and cp.user_id IN (\n18481\n );\n\nSELECT cc.provider, cc.id, p.id, u.*\nFROM users u\nLEFT JOIN crm_profiles p ON u.id = p.user_id\nINNER JOIN teams t ON u.team_id = t.id AND t.status = 'active'\nINNER JOIN crm_configurations cc ON t.crm_id = cc.id\nWHERE u.status = 1\n AND u.deleted_at IS NULL\n AND u.crm_required = 1\n# AND u.team_id = 1\n AND p.id IS NULL -- Move this condition to WHERE clause\nORDER BY u.team_id;\n\nSELECT * FROM opportunities WHERE id = 20002609;\nselect * from teams where id = 1122; # Velatir, 29953 - christian@velatir.com\nselect * from crm_configurations where id = 1060;\nselect * from crm_layouts where crm_configuration_id = 1060;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3596;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 1122 and sa.provider = 'hubspot';\nselect * from opportunities where team_id = 1122 order by updated_at desc;\n\nselect * from crm_field_data where object_type = 'contact';\n\nSELECT * FROM activities WHERE uuid_to_bin('374fc8ed-3315-4c9f-9b25-318b7fd2928f') = uuid; # 76584262\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 248 and sa.provider = 'salesforce';\n\nSELECT * FROM crm_profiles where user_id = 24115; # 005QF000002CswMYAS\nSELECT * FROM users where id = 24115;\nSELECT * FROM accounts where id = 4002896;\nSELECT * FROM teams WHERE name LIKE '%adswerve%';\nSELECT * FROM opportunities where crm_configuration_id = 230 AND crm_provider_id IN (\"0069N000003GIQ9QAO\",\"0061r000019yGP9AAM\",\"0066900001S2KWlAAN\",\"0066900001TDpj2AAD\",\"0066900001b8uEwAAI\",\"0069N000001rQi0QAE\",\"006QF00000KD40mYAD\",\"006QF00000LzpRJYAZ\",\"0069N000002uomtQAA\",\"0069N000002xlMLQAY\",\"0066900001NV6ubAAD\",\"0061r00001HJp45AAD\",\"006QF00000uTlUoYAK\",\"006QF00000v0bZqYAI\");\nSELECT * FROM opportunities WHERE crm_configuration_id = 230 AND crm_provider_id = '0069N000003GIQ9QAO'; # 6272203\n\nSELECT u.id, u.email, ac.name, a.* FROM activities a\nJOIN users u ON a.user_id = u.id\nJOIN accounts ac ON a.account_id = ac.id\nWHERE\nuuid_to_bin('e3269598-b562-44fb-b5e9-9d2694dc63e0') = a.uuid or\nuuid_to_bin('66ddc3ab-4e15-45aa-af0c-248c1eece593') = a.uuid or\nuuid_to_bin('826bd328-e1cc-4213-b8d8-572454cacc07') = a.uuid;\n\nselect * from users where id = 5825;\nSELECT * FROM activities WHERE uuid_to_bin('e56aa2e8-231a-421b-ab1f-cb38ed2bf573') = uuid;\n\nselect * from activities where uuid_to_bin('91e13b2f-2d1b-45f8-b1fd-1141b6563782') = uuid;\n19594, 862\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 862 and sa.provider = 'salesforce';\n\nselect * from automated_reports where id = 36;\nselect ar.frequency, r.*, ar.* from automated_report_results r\njoin automated_reports ar on r.report_id = ar.id\nwhere ar.frequency != 'one_off';\n\nselect s.* from activity_searches s join users u ON s.user_id = u.id where u.team_id = 882;\nselect * from nudges n where n.activity_search_id\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 1065; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 3617;\n\nselect * from users where team_id = 1 and name like '%Lukas%'; # 7160\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\nSELECT * FROM teams WHERE name LIKE '%Integrum ESG%'; # 1126, 1065,\nselect * from opportunities where team_id = 1126;\nSELECT * FROM teams WHERE name LIKE '%Base%'; # 1125, 1063,\nselect * from opportunities where team_id = 1125;\nselect * from contacts c\nwhere c.team_id = 882;\n\nSELECT * FROM activities WHERE id = 76822967;\nSELECT * FROM crm_profiles WHERE user_id = 15440;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 555;\nSELECT * FROM crm_configurations WHERE id = 555;\nSELECT * FROM users WHERE id = 15440; # team. 581, gr. 15440, pl. 3911, act. field 162182\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 581 and sa.provider = 'salesforce';\n\nSELECT * FROM automated_report_results order by id desc;\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556;\n\nselect * from automated_reports where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044 , [\"pdf\",\"podcast\"]\nSELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;\nselect * from automated_report_results order by id desc;\nSELECT * FROM automated_report_results WHERE id = 1919;\n\nselect * from automated_report_results WHERE report_id = 54;\n\nselect * from opportunities where id = 7594349;\n\nSELECT * FROM teams WHERE name LIKE '%Les%'; # 711, 692, 16067ß - jiminnyintegration@lesmills.com\nselect * from playbooks where team_id = 711; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 5515;\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 692;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 711 and sa.provider = 'salesforce';","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"EventSubscriber","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinition","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Service","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivityApiSearch.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"UserOptionsByGroup.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AbstractStageFilterDefinition.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearchServiceProvider.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsPeriodFilterFactory.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsightsPeriodFilterFactoryInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinition.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinitionCollection.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinitionQuery.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilterDefinitionQueryCollection.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FilteredValueContainerInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"IntMinMaxRange.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dtos","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Exceptions","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Jobs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Listeners","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Layout","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"PendingAnalysis","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivityPendingAiAnalysisAfterActivityTypeUpdated.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"ActivityPendingAiAnalysisAfterConferenceImported.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"ActivityPendingAiAnalysisAfterCrmProviderUpdated.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"ActivityPendingAiAnalysisAfterProcessingDoneListener.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"ActivityPendingAiAnalysisAfterProspectAdded.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"EmailActivityPendingAiAnalysisAfterImport.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"OpportunityPendingAiAnalysisAfterStageChanged.php, class","depth":12,"role_description":"text"},{"role":"AXStaticText","text":"HandleCrmFieldValidationFailureListener.php","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ProcessAiAutomationAnalysisResults.php","depth":11,"role_description":"text"}]...
|
6002954523286329432
|
-8464031075924568730
|
visual_change
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-20692-fix Workspace associated with branch 'JY-20692-fix-integration-app-[API_KEY]' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11986 on JY-20692-fix-integration-app-toke…hange, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
33
2
19
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use HubSpot\Client\Crm\Deals\Model\CollectionResponseAssociatedId;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Models\Account;
use Exception;
use Jiminny\Component\DealInsights\Forecast\Forecast;
use Jiminny\Jobs\Crm\MatchActivitiesToNewOpportunity;
use Jiminny\Models\Contact;
use Jiminny\Models\Crm\BusinessProcess;
use Jiminny\Exceptions\CrmException;
use Jiminny\Models\Opportunity;
use Illuminate\Support\Collection;
use Jiminny\Models\Stage;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\DealFieldsService;
use Jiminny\Services\Crm\Hubspot\OpportunitySyncStrategy\HubspotSingleSyncStrategy;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Services\Crm\OpportunitySyncStrategyResolver;
use Jiminny\Utils\CurrencyFormatter;
/**
* Optimized sync methods for better performance
* These methods can be integrated into SyncCrmEntitiesTrait for significant performance gains
*/
trait OpportunitySyncTrait
{
private const int BATCH_SIZE = 100;
private const int BATCH_PROCESS_SIZE = 800;
protected OpportunitySyncStrategyResolver $opportunitySyncStrategyResolver;
protected CrmEntityRepository $crmEntityRepository;
protected DealFieldsService $dealFieldsService;
private ?array $cachedClosedDealStages = null;
private array $cachedBusinessProcesses = [];
private array $cachedStages = [];
public function syncOpportunities(array $parameters, ?string $strategy = null): int
{
$strategies = $this->opportunitySyncStrategyResolver->getStrategies($this->config, $strategy);
$parameters['config'] = $this->config;
$syncCount = 0;
$reportedTotal = 0;
$lastSyncedId = [];
try {
foreach ($strategies as $strategyName => $syncStrategy) {
$this->logger->info(
'[' . $this->getDisplayName() . '] Syncing opportunities using strategy: ' .
$strategyName
);
$total = 0;
$lastId = null;
$buffer = [];
// HubspotWebhookBatchSyncStrategy returns empty generator, this is for other strategies
foreach ($syncStrategy->fetchOpportunities($parameters, $total, $lastId) as $hsOpportunity) {
$buffer[] = $hsOpportunity;
// process every 800 rows (fits < 1 000 association limit)
if (\count($buffer) >= self::BATCH_PROCESS_SIZE) {
$syncCount += $this->processOpportunityBatch($buffer);
$buffer = [];
}
}
// leftovers
if ($buffer) {
$syncCount += $this->processOpportunityBatch($buffer);
}
$reportedTotal += $total;
$lastSyncedId = $lastId;
}
} catch (\HubSpot\Client\Crm\Deals\ApiException | CrmException $e) {
$this->handleSyncException($e, $parameters);
}
$this->logger->info(
'[HubSpot] Synced opportunities',
[
'team' => $this->team->getId(),
'sync_count' => $syncCount,
'total' => $reportedTotal,
'last_synced_id' => $lastSyncedId,
]
);
return $reportedTotal;
}
private function handleSyncException(\Throwable $e, array $parameters): void
{
if (($parameters['since'] ?? null) instanceof Carbon) {
$parameters['since'] = $parameters['since']->toDateTimeString();
}
$parameters['config'] = $this->config->getId();
$this->logger->warning('[' . $this->getDisplayName() . '] Sync opportunities failed', [
'teamId' => $this->team->getUuid(),
'parameters' => $parameters,
'reason' => $e->getMessage(),
]);
}
/**
* @inheritdoc
*/
public function syncOpportunity(string $crmId): ?Opportunity
{
$strategy = $this->opportunitySyncStrategyResolver->resolve(
$this->config,
OpportunitySyncStrategyResolver::SINGLE_SYNC_OPPORTUNITY_STRATEGY,
);
$parameters = [
'config' => $this->config,
'crm_id' => $crmId,
];
try {
if (! $strategy instanceof HubspotSingleSyncStrategy) {
throw new InvalidArgumentException('Strategy must by HubspotSingleSyncStrategy');
}
$hsOpportunity = $strategy->fetchOpportunity($parameters);
} catch (\HubSpot\Client\Crm\Deals\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Opportunity not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
$hsOpportunity['associations'] = $this->convertDealAssociations($hsOpportunity['associations'] ?? []);
return $this->importOrUpdateOpportunity($hsOpportunity);
}
/**
* Process webhook-collected opportunity batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportOpportunityBatch jobs for batch processing.
*
* @return int Number of opportunity IDs dispatched to jobs
*/
public function batchSyncOpportunities(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_DEAL,
$configId
);
}
/**
* Import a batch of opportunities by their CRM IDs.
* Fetches opportunity data from HubSpot API and delegates to importOpportunityBatch().
*
* @param array<string> $crmIds HubSpot deal CRM IDs
*
* @return array{success: array, failed_ids: array, errors?: array<string, string>}
*/
public function importOpportunityBatchByIds(array $crmIds): array
{
$fields = $this->dealFieldsService->getFieldsForConfiguration($this->config);
$allDeals = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
$deals = $this->client->getOpportunitiesByIds($chunk, $fields);
foreach ($deals as $deal) {
$allDeals[] = $deal;
}
}
// IDs not returned by HubSpot are likely deleted or inaccessible deals.
// These are not failures — retrying won't bring them back.
$fetchedIds = array_map('strval', array_column($allDeals, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] CRM IDs not found in HubSpot (likely deleted)', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allDeals),
]);
}
if (empty($allDeals)) {
return ['success' => [], 'failed_ids' => []];
}
return $this->importOpportunityBatch($allDeals);
}
private function getClosedDealStages(): array
{
if ($this->cachedClosedDealStages !== null) {
return $this->cachedClosedDealStages;
}
$stages = $this->crmEntityRepository->getOpportunityClosedStages($this->config);
$data = [
'lost' => [],
'won' => [],
];
foreach ($stages as $stage) {
if ($stage->probability == 0.00) {
$data['lost'][] = $stage->crm_provider_id;
}
if ($stage->probability == 100.00) {
$data['won'][] = $stage->crm_provider_id;
}
}
$this->cachedClosedDealStages = $data;
return $data;
}
/**
* Import deals into the database with pre-fetched associations.
*
* API calls here (getAssociationsData, getExistingOpportunityCrmIds) are NOT
* caught — if they throw, the exception propagates to ImportOpportunityBatch::handle()
* where Laravel retries the whole job with backoff. After all retries exhausted,
* failed() requeues all IDs to Redis.
*
* The per-deal loop catches exceptions individually. A deal can end up in three states:
* - success: imported/updated successfully
* - failed_ids: exception thrown (DB constraint violation, corrupt data, etc.)
* These are permanent issues — retrying won't fix them.
* - skipped (null): missing dependencies (no account, unknown pipeline/stage).
* This is acceptable — the deal cannot be imported until those exist.
*/
private function importOpportunityBatch(array $deals): array
{
$syncedOpportunities = [
'success' => [],
'failed_ids' => [],
];
$dealIds = array_column($deals, 'id');
// Shared association/existing-ID preparation is batch-level state. If it fails, rethrow so the
// queue job retries the whole batch and eventually requeues all deal IDs back to Redis.
try {
$companyAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'companies');
$contactAssociations = $this->client->getAssociationsData($dealIds, 'deals', 'contacts');
$associationsData = $this->prepareAssociatedEntities($companyAssociations, $contactAssociations);
$existingCrmIds = $this->crmEntityRepository->getExistingOpportunityCrmIds(
$this->config,
array_map('strval', $dealIds)
);
$existingCrmIdSet = array_flip($existingCrmIds);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to fetch associations or existing IDs', [
'teamId' => $this->team->getId(),
'dealCount' => count($dealIds),
'error' => $e->getMessage(),
]);
throw $e;
}
foreach ($deals as $deal) {
try {
$deal['associations'] = $this->prepareAssociationsForOpportunity(
$deal['id'],
$companyAssociations,
$contactAssociations,
$associationsData
);
$syncedOpportunity = $this->importOrUpdateOpportunity(
$deal,
isset($existingCrmIdSet[(string) $deal['id']])
);
if ($syncedOpportunity) {
$syncedOpportunities['success'][] = $syncedOpportunity;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import opportunity', [
'teamId' => $this->team->getId(),
'crmId' => $deal['id'],
'error' => $e->getMessage(),
]);
$syncedOpportunities['failed_ids'][] = $deal['id'];
$syncedOpportunities['errors'][$deal['id']] = $e->getMessage();
}
}
return $syncedOpportunities;
}
/**
* Prepare associated entities for opportunities with optimized batch processing
* Returns structured data with CRM ID to DB ID mappings for each opportunity
*/
private function prepareAssociatedEntities(array $companyAssociations, array $contactAssociations): array
{
// Step 1: Collect all unique company and contact IDs from associations
$allCompanyIds = $this->flattenAssociationIds($companyAssociations);
$allContactIds = $this->flattenAssociationIds($contactAssociations);
// Step 2: Batch sync missing entities and get CRM ID to DB ID mappings
$companyIdMappings = [];
$contactIdMappings = [];
if (! empty($allCompanyIds)) {
$companyIdMappings = $this->prepareAssociatedAccounts($allCompanyIds);
}
if (! empty($allContactIds)) {
$contactIdMappings = $this->prepareAssociatedContacts($allContactIds);
}
return [
'company_id_mappings' => $companyIdMappings,
'contact_id_mappings' => $contactIdMappings,
];
}
/**
* Flatten association data to get unique IDs
*/
private function flattenAssociationIds(array $associations): array
{
$ids = [];
foreach ($associations as $dealAssociations) {
if (is_array($dealAssociations)) {
foreach ($dealAssociations as $id) {
$ids[$id] = true;
}
}
}
return array_keys($ids);
}
/**
* Batch sync missing accounts
*/
private function prepareAssociatedAccounts(array $companyIds): array
{
// Find which accounts already exist
$existingAccounts = $this->crmEntityRepository
->findAccountsByExternalIds($this->config, $companyIds);
$existingCompanyIds = $existingAccounts->pluck('crm_provider_id')->toArray();
$existingAccountsData = $existingAccounts->mapWithKeys(function ($account) {
return [$account->getCrmProviderId() => $account->getId()];
})->toArray();
$missingCompanyIds = array_diff($companyIds, $existingCompanyIds);
if (empty($missingCompanyIds)) {
return $existingAccountsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts', [
'teamId' => $this->team->getUuid(),
'total_companies' => count($companyIds),
'existing_companies' => count($existingCompanyIds),
'missing_companies' => count($missingCompanyIds),
]);
// we already have limit on opportunity ids count
// Initialize variable before try block
$syncedAccountsData = [];
try {
$syncedAccountsData = $this->batchSyncCrmObjects('companies', $missingCompanyIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing accounts', [
'size' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
$syncedAccountsData = [];
}
return $existingAccountsData + $syncedAccountsData;
}
/**
* Prepare associated contacts - find existing and sync missing ones
* Returns mapping of CRM ID to DB ID
*/
private function prepareAssociatedContacts(array $contactIds): array
{
// Find which contacts already exist
$existingContacts = $this->crmEntityRepository
->findContactsByExternalIds($this->config, $contactIds);
$existingContactIds = $existingContacts->pluck('crm_provider_id')->toArray();
// Create mapping for existing contacts
$existingContactsData = $existingContacts->mapWithKeys(function ($contact) {
return [$contact->getCrmProviderId() => $contact->getId()];
})->toArray();
$missingContactIds = array_diff($contactIds, $existingContactIds);
if (empty($missingContactIds)) {
return $existingContactsData;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing contacts', [
'teamId' => $this->team->getUuid(),
'total_contacts' => count($contactIds),
'existing_contacts' => count($existingContactIds),
'missing_contacts' => count($missingContactIds),
]);
// Sync missing contacts using batch API
try {
$syncedContactsData = $this->batchSyncCrmObjects('contacts', $missingContactIds);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to sync missing contacts', [
'size' => count($missingContactIds),
'error' => $e->getMessage(),
]);
$syncedContactsData = [];
}
return $existingContactsData + $syncedContactsData;
}
private function batchSyncCrmObjects(string $objectType, array $crmIds): array
{
$syncObjects = [];
$crmObjectIds = array_values($crmIds);
foreach (array_chunk($crmObjectIds, self::BATCH_SIZE) as $chunk) {
try {
$objects = $objectType === 'companies' ?
$this->client->getCompaniesByIds($chunk, $this->getCompanyFields()) :
$this->client->getContactsByIds($chunk, $this->getContactFields());
foreach ($objects as $objectId => $objectData) {
$this->importCrmObject($objectType, (string) $objectId, $objectData, $syncObjects);
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch synced ' . $objectType, [
'requested_count' => count($chunk),
'synced_count' => count($objects),
]);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch ' . $objectType . ' sync failed', [
'ids' => $chunk,
'error' => $e->getMessage(),
]);
}
}
return $syncObjects;
}
private function importCrmObject(string $objectType, string $objectId, mixed $objectData, array &$syncObjects): void
{
try {
$object = $objectType === 'companies' ?
$this->importAccount($objectData) :
$this->importContact($objectData);
if ($object) {
$syncObjects[$object->getCrmProviderId()] = $object->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import batch ' . $objectType, [
'id' => $objectId,
'error' => $e->getMessage(),
]);
}
}
/**
* Prepare associations for a single opportunity
*
* The return value is an array with the following structure:
* [
* 'companies' => [
* $companyCrmId => $companyId,
* ...
* ],
* 'contacts' => [
* $contactCrmId => $contactId,
* ...
* ],
* 'account_id' => $accountId,
* ]
*/
private function prepareAssociationsForOpportunity(
string $oppCrmId,
array $companyAssociations,
array $contactAssociations,
array $associationsData
): array {
$associations = [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
$oppCompanyIds = $companyAssociations[$oppCrmId] ?? [];
foreach ($oppCompanyIds as $companyCrmId) {
if (isset($associationsData['company_id_mappings'][$companyCrmId])) {
$associations['companies'][$companyCrmId] = $associationsData['company_id_mappings'][$companyCrmId];
// Set primary account (first company becomes primary account)
if ($associations['account_id'] === null) {
$associations['account_id'] = $associationsData['company_id_mappings'][$companyCrmId];
}
}
}
$oppContactIds = $contactAssociations[$oppCrmId] ?? [];
foreach ($oppContactIds as $contactCrmId) {
if (isset($associationsData['contact_id_mappings'][$contactCrmId])) {
$associations['contacts'][$contactCrmId] = $associationsData['contact_id_mappings'][$contactCrmId];
}
}
return $associations;
}
/**
* Update only associations for an opportunity
*/
private function updateOpportunityAssociations(Opportunity $opportunity, array $associations): void
{
// Update contact associations
$this->importOpportunityContacts($opportunity, $associations['contacts']);
// Update company (account) associations
$this->updateOpportunityAccount($opportunity, $associations['account_id']);
}
/**
* Remove all contact associations from an opportunity
*/
private function removeAllOpportunityContacts(Opportunity $opportunity): void
{
$currentCount = (int) $opportunity->contacts()->count();
if ($currentCount > 0) {
$opportunity->contacts()->detach();
$this->logger->info('[' . $this->getDisplayName() . '] Removed all contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_count' => $currentCount,
]);
}
}
private function updateOpportunityAccount(Opportunity $opportunity, ?int $accountId): void
{
if ($accountId === null) {
// No account ID provided - keep current account
return;
}
$currentAccountId = $opportunity->getAccountId();
// Only update if account has changed
if ($currentAccountId !== $accountId) {
$opportunity->account_id = $accountId;
$opportunity->save();
$this->logger->info('[' . $this->getDisplayName() . '] Updated opportunity account association', [
'opportunity_id' => $opportunity->getId(),
'old_account_id' => $currentAccountId,
'new_account_id' => $accountId,
]);
}
}
/**
* Find existing opportunities by external IDs (OPTIMIZED VERSION)
* Uses batch query for better performance
*/
private function findExistingOpportunities(array $crmIds): Collection
{
return $this->crmEntityRepository
->findOpportunitiesByExternalIds($this->config, $crmIds);
}
private function processOpportunityBatch(array $opportunities): int
{
$syncedOpportunities = $this->importOpportunityBatch($opportunities);
return count($syncedOpportunities['success'] ?? []);
}
/**
* Convert single deal associations from HubSpot format to internal format
* Handles both HubSpot SDK objects and array formats
*
* @param array $opportunityAssociations Raw associations from HubSpot API or pre-processed
*
* @return array Processed associations with DB IDs
*/
private function convertDealAssociations(array $opportunityAssociations): array
{
$associations = $this->initializeAssociationsStructure();
if (empty($opportunityAssociations)) {
return $associations;
}
$associationIds = $this->extractAssociationIds($opportunityAssociations);
$this->processCompanyAssociations($associationIds, $associations);
$this->processContactAssociations($associationIds, $associations);
return $associations;
}
private function initializeAssociationsStructure(): array
{
return [
'companies' => [],
'contacts' => [],
'account_id' => null, // Primary account for opportunity
];
}
private function extractAssociationIds(array $opportunityAssociations): array
{
$associationIds = [];
foreach ($opportunityAssociations as $type => $associationData) {
if (! empty($associationData)) {
$associationIds[$type] = $this->convertSingleDealAssociations($associationData);
}
}
return $associationIds;
}
private function processCompanyAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['companies'])) {
return;
}
$companyId = $associationIds['companies'][0];
$account = $this->findOrSyncAccount($companyId);
if ($account instanceof Account) {
$associations['companies'][$companyId] = $account->getId();
$associations['account_id'] = $account->getId();
}
}
private function processContactAssociations(array $associationIds, array &$associations): void
{
if (empty($associationIds['contacts'])) {
return;
}
foreach ($associationIds['contacts'] as $contactId) {
$contact = $this->findOrSyncContact($contactId);
if ($contact instanceof Contact) {
$associations['contacts'][$contactId] = $contact->getId();
}
}
}
private function findOrSyncAccount(string $companyId): ?Account
{
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $companyId);
if (! $account instanceof Account) {
$account = $this->syncAccount($companyId);
}
return $account;
}
private function findOrSyncContact(string $contactId): ?Contact
{
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $contactId);
if (! $contact instanceof Contact) {
$contact = $this->syncContact($contactId);
}
return $contact;
}
private function convertSingleDealAssociations($opportunityAssociations = null): array
{
$associationData = [];
if ($opportunityAssociations === null) {
return $associationData;
}
// Handle array input (from extractAssociationIds)
if (is_array($opportunityAssociations)) {
return $opportunityAssociations;
}
// Handle CollectionResponseAssociatedId object
if ($opportunityAssociations instanceof CollectionResponseAssociatedId) {
foreach ($opportunityAssociations->getResults() as $association) {
$associationData[] = $association->getId();
}
}
return $associationData;
}
private function importOrUpdateOpportunity($crmData, ?bool $exists = null): ?Opportunity
{
if (empty($crmData['properties'])) {
return null;
}
$crmId = (string) $crmData['id'];
$properties = $crmData['properties'];
$associations = $crmData['associations'] ?? [];
$opportunityExists = $exists ?? (bool) $this->crmEntityRepository->findOpportunityByExternalId(
$this->config,
$crmId
);
if ($opportunityExists) {
return $this->updateOpportunity($crmId, $properties, $associations);
} else {
return $this->createOpportunity($crmId, $properties, $associations);
}
}
/**
* Create new opportunity
*/
private function createOpportunity(string $crmId, array $properties, array $associations): ?Opportunity
{
$accountId = $this->resolveAccountId($associations);
if (! $accountId) {
return null;
}
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
if (! $businessProcess) {
return null;
}
$stage = $this->resolveStage($businessProcess, $properties['dealstage'] ?? null);
if (! $stage) {
return null;
}
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->importOpportunityContacts($opportunity, $associations['contacts']);
if ($opportunity->wasRecentlyCreated) {
MatchActivitiesToNewOpportunity::dispatch($opportunity->getId());
}
return $opportunity;
}
/**
* Update existing opportunity
*/
private function updateOpportunity(string $crmId, array $properties, array $associations): Opportunity
{
$accountId = $this->resolveAccountId($associations);
$businessProcess = $this->resolveBusinessProcess($properties['pipeline'] ?? null);
$stage = $businessProcess ? $this->resolveStage($businessProcess, $properties['dealstage'] ?? null) : null;
$data = $this->buildOpportunityData($properties, $accountId, $businessProcess, $stage);
$attributes = [
'crm_configuration_id' => $this->config->getId(),
'crm_provider_id' => $crmId,
];
$values = array_merge($attributes, $data);
$opportunity = $this->crmEntityRepository->upsertOpportunity($attributes, $values);
$this->importExternalFieldData($properties, $opportunity->getId());
$this->updateOpportunityAssociations($opportunity, $associations);
return $opportunity;
}
private function resolveAccountId(array $associations): ?int
{
if (! empty($associations['accountId'])) {
return $associations['accountId'];
}
if (empty($associations)) {
return null;
}
// we can't resolve multiple account ids (currently SDK returns one company)
foreach ($associations['companies'] as $accountId) {
return $accountId;
}
return null;
}
private function buildOpportunityData(
array $properties,
?int $accountId,
?BusinessProcess $businessProcess,
?Stage $stage
): array {
$ownerId = null;
$profile = null;
if (! empty($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$name = 'Unknown';
if (isset($properties['dealname'])) {
$name = mb_strimwidth($properties['dealname'], 0, 128);
}
$amount = $this->resolveAmount($properties);
$currency = $properties['deal_currency_code'] ?? null;
$closeDate = null;
if (! empty($properties['closedate'])) {
$closeDate = Carbon::parse($properties['closedate'])->format('Y-m-d');
}
$remotelyCreatedAt = null;
if (! empty($properties['createdate']) && strtotime($properties['createdate'])) {
$date = $this->parseCleanDatetime($properties['createdate']);
$remotelyCreatedAt = $date?->format('Y-m-d H:i:s');
}
$closedStages = $this->getClosedDealStages();
$isWon = in_array($properties['dealstage'], $closedStages['won']);
$isLost = in_array($properties['dealstage'], $closedStages['lost']);
$data = [
'team_id' => $this->team->getId(),
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => $name,
'value' => ! empty($amount) ? $amount : null,
'currency_code' => CurrencyFormatter::formatCode($currency),
'close_date' => $closeDate,
'is_closed' => $isWon || $isLost,
'is_won' => $isWon,
'remotely_created_at' => $remotelyCreatedAt,
'probability' => $this->resolveDealProbability($properties['hs_deal_stage_probability']),
'forecast_category' => $this->resolveForecastCategory($properties['hs_manual_forecast_category']),
];
if ($accountId) {
$data['account_id'] = $accountId;
}
if ($stage) {
$data['stage_id'] = $stage->id;
}
if ($businessProcess) {
$recordType = $this->crmEntityRepository->getBusinessProcessRecordType($businessProcess);
if ($recordType) {
$data['record_type_id'] = $recordType->id;
}
}
return $data;
}
private function resolveBusinessProcess(?string $pipelineId): ?BusinessProcess
{
if ($pipelineId === null) {
return null;
}
if (isset($this->cachedBusinessProcesses[$pipelineId])) {
return $this->cachedBusinessProcesses[$pipelineId];
}
$businessProcess = $this->getBusinessProcess($pipelineId);
if (! $businessProcess instanceof BusinessProcess) {
$this->importStages();
$businessProcess = $this->getBusinessProcess($pipelineId);
}
if (! $businessProcess instanceof BusinessProcess) {
$this->logger->info(
'[HubSpot] Deal is not attached to a pipeline',
[
'pipeline' => $pipelineId]
);
}
$this->cachedBusinessProcesses[$pipelineId] = $businessProcess;
return $businessProcess;
}
private function getBusinessProcess(string $pipelineId): ?BusinessProcess
{
return $this->crmEntityRepository->findBusinessProcessesByExternalId($this->config, $pipelineId);
}
private function resolveStage(BusinessProcess $businessProcess, ?string $stageId): ?Stage
{
if (empty($stageId)) {
return null;
}
$cacheKey = $businessProcess->getId() . ':' . $stageId;
if (isset($this->cachedStages[$cacheKey])) {
return $this->cachedStages[$cacheKey];
}
$stage = $this->crmEntityRepository->getPipelineStageByConditions(
$businessProcess,
[
'crm_provider_id' => $stageId,
'type' => Stage::TYPE_OPPORTUNITY,
]
);
if ($stage === null) {
$this->importStages(null, $stageId);
}
if ($stage === null) {
$this->logger->info('[HubSpot] Stage does not exist => ' . $stageId);
}
$this->cachedStages[$cacheKey] = $stage;
return $stage;
}
private function resolveAmount(array $properties): ?string
{
$amount = null;
if (! empty($properties['amount'])) {
$amount = str_replace(',', '', $properties['amount']);
}
if ($this->config->hasDefaultCurrencyFieldSet()) {
$valueFieldName = $this->config->getDefaultCurrencyField()->getCrmProviderId();
$amount = $properties[$valueFieldName] ?? $amount;
}
return $amount;
}
private function parseCleanDatetime(string $datetime): ?Carbon
{
// Treat pre-1980 values as invalid
$minValidDate = Carbon::parse('1980-01-01 00:00:00');
try {
$date = Carbon::parse($datetime);
if ($minValidDate->gt($date)) {
return null;
}
return $date;
} catch (Exception) {
return null; // On parse error, treat as null
}
}
private function resolveDealProbability(?string $stageProbability): int
{
if ($stageProbability === null) {
return 0;
}
$probability = (float) $stageProbability;
return $probability > 1 ? 0 : (int) ($probability * 100);
}
private function resolveForecastCategory(?string $forecastCategory): string
{
if (! $forecastCategory) {
return Forecast::FORECAST_CATEGORY_UNCATEGORIZED;
}
$forecastCategory = str_replace('_', ' ', $forecastCategory);
return ucwords(strtolower($forecastCategory));
}
private function importExternalFieldData(array $properties, int $opportunityId): void
{
$crmFields = $this->getOpportunitySyncableFields();
$this->importOpportunityCrmFieldData($properties, $crmFields, $opportunityId);
}
private function importOpportunityContacts(Opportunity $opportunity, array $associations): void
{
// Handle empty or missing contact associations
if (empty($associations)) {
// Remove all existing contact associations if none provided
$this->removeAllOpportunityContacts($opportunity);
return;
}
// Use differential sync approach for better performance and accuracy
$this->syncOpportunityContactsDifferential($opportunity, $associations);
}
/**
* Sync opportunity contacts using differential approach
* This compares current vs new associations and only makes necessary changes
*/
private function syncOpportunityContactsDifferential(Opportunity $opportunity, array $contactAssociations): void
{
$currentContactCrmIds = $this->getCurrentContactCrmIds($opportunity);
$contactAssociationIds = array_keys($contactAssociations);
$contactsToAdd = array_diff($contactAssociationIds, $currentContactCrmIds);
$contactsToRemove = array_diff($currentContactCrmIds, $contactAssociationIds);
if (empty($contactsToAdd) && empty($contactsToRemove)) {
return;
}
$this->logContactAssociationChanges($opportunity, $currentContactCrmIds, $contactAssociations, $contactsToAdd, $contactsToRemove);
$this->removeContactAssociations($opportunity, $contactsToRemove);
$this->addContactAssociations($opportunity, $contactsToAdd, $contactAssociations);
}
private function getCurrentContactCrmIds(Opportunity $opportunity): array
{
return $opportunity->contacts()
->pluck('contacts.crm_provider_id')
->toArray();
}
private function logContactAssociationChanges(
Opportunity $opportunity,
array $currentContactCrmIds,
array $contactAssociations,
array $contactsToAdd,
array $contactsToRemove
): void {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association changes', [
'opportunity_id' => $opportunity->getId(),
'current_contacts' => $currentContactCrmIds,
'new_contacts' => $contactAssociations,
'contacts_to_add' => $contactsToAdd,
'contacts_to_remove' => $contactsToRemove,
]);
}
private function removeContactAssociations(Opportunity $opportunity, array $contactsToRemove): void
{
if (empty($contactsToRemove)) {
return;
}
$contactsToDetach = $opportunity->contacts()
->whereIn('contacts.crm_provider_id', $contactsToRemove)
->pluck('contacts.id')
->toArray();
if (! empty($contactsToDetach)) {
$opportunity->contacts()->detach($contactsToDetach);
$this->logger->info('[' . $this->getDisplayName() . '] Removed contact associations', [
'opportunity_id' => $opportunity->getId(),
'removed_contact_crm_ids' => $contactsToRemove,
'removed_contact_count' => count($contactsToDetach),
]);
}
}
private function addContactAssociations(Opportunity $opportunity, array $contactsToAdd, array $contactAssociations): void
{
if (empty($contactsToAdd)) {
return;
}
$contactsAdded = [];
foreach ($contactsToAdd as $crmId) {
$id = $contactAssociations[$crmId];
if ($this->attachSingleContact($opportunity, (string) $crmId, $id)) {
$contactsAdded[] = $crmId;
}
}
$this->logAddedContacts($opportunity, $contactsAdded);
}
private function attachSingleContact(Opportunity $opportunity, string $crmId, int $id): bool
{
try {
$contact = $this->crmEntityRepository->findContactByConfigurationAndId($this->config, $id);
if (! $contact) {
return false;
}
return $this->performContactAttachment($opportunity, $contact, $crmId);
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to add contact association', [
'opportunity_id' => $opportunity->getId(),
'contact_crm_id' => $crmId,
'error' => $e->getMessage(),
]);
return false;
}
}
private function performContactAttachment(Opportunity $opportunity, Contact $contact, string $crmId): bool
{
try {
$opportunity->contacts()->attach($contact->getId(), [
'crm_provider_id' => $crmId,
]);
return true;
} catch (\Illuminate\Database\QueryException $e) {
if (str_contains($e->getMessage(), 'Duplicate entry')) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact association already exists', [
'contact_id' => $contact->getId(),
'contact_crm_id' => $crmId,
'opportunity_id' => $opportunity->getId(),
]);
return false;
}
throw $e;
}
}
private function logAddedContacts(Opportunity $opportunity, array $contactsAdded): void
{
if (! empty($contactsAdded)) {
$this->logger->info('[' . $this->getDisplayName() . '] Added contact associations', [
'opportunity_id' => $opportunity->getId(),
'contacts_to_add_count' => count($contactsAdded),
'added_contact_crm_ids' => $contactsAdded,
'added_contacts_count' => count($contactsAdded),
]);
}
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
34
1
34
62
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM teams WHERE name LIKE '%litify%'; # 1069, 994, 24993
SELECT * FROM users WHERE id = 25061;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 994;
SELECT * FROM crm_profiles WHERE user_id = 25061;
select * from crm_configurations where id = 834;
SELECT * FROM teams WHERE id = 882;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 882 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations where id = 882; # 933 - GoGlobal
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE provider = 'hubspot' and crm_provider_id = 7270388;
SELECT * FROM contacts where crm_configuration_id = 834;
SELECT * FROM opportunities WHERE team_id = 933
# AND crm_provider_id IN ('20131586060','46017317898','52543911090','53451356564','54101251892','54323768459');
AND id IN (8482561,18352941,19042734,19232139,19445140,19472541);
SELECT * FROM opportunity_contacts
WHERE opportunity_id IN (8482561,18352941,19042734,19232139,19445140,19472541);
# [PASSWORD_DOTS]
SELECT * FROM crm_configurations where id = 485; #
SELECT * FROM opportunities WHERE team_id = 933 order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 933 and sa.provider = 'hubspot';
select crm.provider, l.* from leads l join crm_configurations crm on l.crm_configuration_id = crm.id
where crm.provider NOT IN ('salesforce', 'integration-app', 'bullhorn', 'copper')
# and l.converted_at IS NOT NULL
;
# [PASSWORD_DOTS]
SELECT * FROM activities a WHERE type IN ('email-inbound', 'email-outbound')
and opportunity_id IS NULL
order by id desc;
SELECT * FROM teams WHERE id = 604; # 598
SELECT * FROM activities WHERE id = 74410828; # [EMAIL]
SELECT * FROM accounts WHERE id = 20068382;
SELECT * FROM accounts WHERE id = 35186038;
SELECT * FROM contacts WHERE team_id = 852 and updated_at > '2026-01-23 12:30:00' order by updated_at desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 559 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('cb6342b6-a183-401c-b0af-ede92b2ae763') = uuid;
select * from sidekick_settings where team_id = 781;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 26651871; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 7562435;
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8420347; # opflit 2100
SELECT * FROM crm_layouts WHERE crm_configuration_id = 711;
SELECT * FROM activities where crm_configuration_id = 711 and crm_provider_id IS NULL
and is_internal = 0 and status = 'completed'
order by id desc;
SELECT * FROM crm_layout_entities
WHERE crm_layout_id IN (2352, 2353);
;
SELECT * FROM crm_configurations where provider = 'hubspot' and id = 530;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 556 and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('c6ca4b22-7738-4563-a95d-b8a9598924ae') = uuid;
SELECT * FROM activities WHERE uuid_to_bin('442abb2b-28bd-4be8-9c25-19e9bf02766d') = uuid;
select * from contacts
where crm_configuration_id = 530
and crm_provider_id = 872252;
select * from activities where crm_configuration_id = 530
and user_id = 14343 and type like '%softphone%'
and created_at between '2026-01-28 15:00:00' and '2026-01-28 15:10:00';
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 25666868; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id = 8646335; # Teya
SELECT * FROM crm_configurations where provider = 'hubspot' and crm_provider_id IN (5933397);
SELECT t.name, t.id, t.owner_id, c.id, c.provider, c.crm_base_url FROM teams t
JOIN crm_configurations c ON t.id = c.team_id
WHERE t.status = 'active';
SELECT * FROM teams where id = 1091;
SELECT * FROM crm_configurations where team_id = 1091;
SELECT * FROM activity_providers where team_id = 1091;
SELECT * FROM activities where crm_configuration_id = 1024 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT * FROM teams WHERE name LIKE '%Leadventure%';
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u on u.id = sa.sociable_id
JOIN teams t on t.id = u.team_id
WHERE u.team_id = 1091 and sa.provider = 'salesforce';
SELECT * FROM teams WHERE name LIKE '%Wilson%'; # 862, 812
SELECT * FROM teams where id = 862;
SELECT * FROM crm_configurations where team_id = 862;
SELECT * FROM activity_providers where team_id = 862;
SELECT * FROM activities where crm_configuration_id = 812 and type IN ('softphone', 'softphone-outbound')
and provider NOT IN ('hubspot', 'aircall')
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by id desc;
SELECT t.id, crm.id, crm.provider, ap.* FROM teams t
join crm_configurations crm on t.id = crm.team_id
join activity_providers ap on t.id = ap.team_id
where t.status = 'active' and ap.is_enabled = 1
and crm.provider = 'hubspot'
and ap.provider NOT IN ('hubspot', 'aircall', 'uploader', 'gong', 'twilio', 'zoom-bot', 'google-meet', 'ms-teams',
'outreach', 'close', 'ringcentral', 'dialpad', 'zoom-phone');
SELECT * FROM teams where id = 1068;
SELECT * FROM crm_configurations where team_id = 1068;
SELECT * FROM activity_providers where team_id = 1068;
SELECT * FROM activities a
where crm_configuration_id = 993 and type IN ('softphone', 'softphone-outbound')
and a.provider NOT IN ('hubspot', 'uploader', 'gong', 'twilio', 'google-meet', 'ms-teams','close'
)
# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'
order by a.id desc;
SELECT
CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,
u.email,
sa.*,
t.owner_id FROM social_accounts sa
JOIN users u ...
|
NULL
|
|
69339
|
1592
|
6
|
2026-04-22T07:57:23.864859+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-22/1776 /Users/lukas/.screenpipe/data/data/2026-04-22/1776844643864_m1.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-20372-ai- Workspace associated with branch 'JY-20372-ai-reports-promotion-pages' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Pause
Analyzing project…
app/app/Providers/EventServiceProvider.php
Checked out JY-20372-ai-reports-promotion-pages
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
#11998 on JY-20372-ai-reports-promotion-pages, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-20372-ai-reports-promotion-pages' has been restored","depth":3,"value":"Workspace associated with branch 'JY-20372-ai-reports-promotion-pages' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.0,"top":0.0,"width":0.034027778,"height":0.018888889},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Pause","depth":2,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzing project…","depth":2,"role_description":"text"},{"role":"AXStaticText","text":"app/app/Providers/EventServiceProvider.php","depth":2,"role_description":"text"},{"role":"AXTextField","text":"Checked out JY-20372-ai-reports-promotion-pages","depth":3,"value":"Checked out JY-20372-ai-reports-promotion-pages","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11998 on JY-20372-ai-reports-promotion-pages, menu","depth":5,"help_text":"Pull request #11998 exists for current branch JY-20372-ai-reports-promotion-pages","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
2885554670636836074
|
-7195228028181026234
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-20372-ai- Workspace associated with branch 'JY-20372-ai-reports-promotion-pages' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Pause
Analyzing project…
app/app/Providers/EventServiceProvider.php
Checked out JY-20372-ai-reports-promotion-pages
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
#11998 on JY-20372-ai-reports-promotion-pages, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
69338
|
|
55696
|
1199
|
29
|
2026-04-20T10:01:48.159949+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776679308159_m1.jpg...
|
PhpStorm
|
faVsco.js – AskJiminnyReportActivityServiceTest.ph faVsco.js – AskJiminnyReportActivityServiceTest.php...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","depth":3,"value":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.0,"top":0.0,"width":0.034027778,"height":0.018888889},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dev","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dialers","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playlists","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Postmark","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"role_description":"text"}]...
|
-1116408377427364719
|
941379111497438610
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class...
|
NULL
|
|
55695
|
1199
|
28
|
2026-04-20T10:01:47.275010+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776679307275_m1.jpg...
|
PhpStorm
|
faVsco.js – AskJiminnyReportActivityServiceTest.ph faVsco.js – AskJiminnyReportActivityServiceTest.php...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
CoachingFeedbacksUpdateEsActivities.php, class
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IterateUsersCommand.php, abstract class
JiminnyCacheClearCommand.php, class
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","depth":3,"value":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.0,"top":0.0,"width":0.034027778,"height":0.018888889},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dev","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dialers","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playlists","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Postmark","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedbacksUpdateEsActivities.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"role_description":"text"}]...
|
-6914476808652043166
|
384199395082966417
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
CoachingFeedbacksUpdateEsActivities.php, class
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IterateUsersCommand.php, abstract class
JiminnyCacheClearCommand.php, class
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class...
|
55690
|
|
69207
|
1585
|
23
|
2026-04-22T07:40:09.247390+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-22/1776 /Users/lukas/.screenpipe/data/data/2026-04-22/1776843609247_m2.jpg...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out JY-18909-automated-reports-ask-jiminny
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"value":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.09773936,"height":0.040702313},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.017287234,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"bounds":{"left":0.89793885,"top":0.9481245,"width":0.023603724,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.016289894,"height":0.0},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Checked out JY-18909-automated-reports-ask-jiminny","depth":3,"bounds":{"left":0.24900267,"top":0.858739,"width":0.12167553,"height":0.016759777},"value":"Checked out JY-18909-automated-reports-ask-jiminny","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.24900267,"top":0.8603352,"width":0.027260639,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.27792552,"top":0.8603352,"width":0.0944149,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.25797874,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.29654256,"top":0.019952115,"width":0.12732713,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzing…","depth":4,"bounds":{"left":0.6981383,"top":0.19952115,"width":0.019946808,"height":0.015163607},"role_description":"text"},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"8","depth":4,"bounds":{"left":0.9644282,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.9740692,"top":0.09896249,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.98138297,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-721159764986472535
|
-1701118961565976855
|
visual_change
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out JY-18909-automated-reports-ask-jiminny
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
...
|
NULL
|
|
55698
|
1200
|
49
|
2026-04-20T10:01:50.121688+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776679310121_m2.jpg...
|
PhpStorm
|
faVsco.js – AskJiminnyReportActivityServiceTest.ph faVsco.js – AskJiminnyReportActivityServiceTest.php...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
Stop 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
CoachingFeedbacksUpdateEsActivities.php, class
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IterateUsersCommand.php, abstract class
JiminnyCacheClearCommand.php, class
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php, class
RemoveExpiredNudgesCommand.php, class
RemoveUnusedParticipantSpeechesCommand.php, class
ResetElasticSearch.php, class
RestoreActivityCrmProviderIdCommand.php, class
RestoreActivityTypeCommand.php, class
SeedActivities.php, class
SyncActivity.php, class
TrackImported.php, class
UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php, class
WhichWorkerIsWorkingOnWhichJob.php, class
Scheduling, folder
Kernel.php, class
Contracts, folder
Domain, folder
DTO, folder
Emails, folder
Enums, folder
Events, folder
Exceptions, folder
FFMpeg, folder
Formats, folder
Guards, folder
Helpers, folder
Http, folder
AccessTokenProvider, folder
Controllers, folder
API, folder
Auth, folder
CustomerApi, folder
Internal, folder
Kiosk, folder
Settings, folder
Telephony, folder
Webhook, folder
Hubspot, folder
IntegrationAppSubscriptions, folder
ActivityProviderController.php, class
ActivityTranscriptionController.php, class
BaseController.php, class
CalendarController.php, final class
ReportController.php, class
SoftphoneWebhookController.php, final class
AbstractController.php, abstract class
CommentContextInterface.php, interface
ConferencesOptInOutController.php, class
Controller.php, class
ExportController.php, final class
FrontendController.php, class
FrontendControllerTrait.php
GeocodingController.php, final class
HealthCheckController.php, class
LiveCoachController.php, final class
MissingTeamController.php, class
MobileController.php, class
NotificationController.php, class
NotificationProviderController.php, class
PlaybackController.php, final class
PlaylistController.php, final class
PusherController.php, class
SlackController.php, final class
SupportController.php, final class
TeamSetupController.php, class
UserAutomatedReportsController.php, class
WelcomeController.php, class
Middleware, folder
Requests, folder
Resources, folder
Responses, folder
Serializers, folder
Transformers, folder
Kernel.php, class
PlaylistTrackResourceTrait.php
ValidateCrmConnectionRequiredTrait.php
Integrations, folder
Interactions, folder
Jobs, folder
Listeners, folder
Activities, folder
Authentication, folder
AutomatedReports, folder
Calendars, folder
Crm, folder
BootstrapIntegrationApp.php
ImportActivityTypes.php, class
ImportMetadata.php, class
InitProfiles.php, class
LayoutModifiedListener.php, class
LayoutUpdatedListener.php, class
RematchActivityOnCrmObjectDetach.php
RemoteCrmRecordDeletedListener.php, class
ResolveOwner.php, class
SyncOpportunity.php, class
SyncProfileLeads.php
SyncProfileOpportunities.php, class
DealRisks, folder
ElasticSearch, folder
Groups, folder
Import, folder
Mailbox, folder
Nudges, folder
Opportunities, folder
Playbooks, folder
Playlists, folder
Teams, folder
Transcription, folder
Users, folder
Webhook, folder
.gitkeep
ChangeLogContextCorrelationId.php, class
Mail, folder
Models, folder
Activity, folder
Ai, folder
AskAnything, folder
Calendar, folder
Connection, folder
Contracts, folder
Crm, folder
BusinessProcess.php, class
Configuration.php, class
ContactRole.php, class
Field.php, class
FieldData.php, class
FieldValue.php, class
Layout.php, class
LayoutEntity.php, class
Log.php, class
Profile.php, class
RecordType.php, class
SyncBatch.php, class
ElasticSearch, folder
Feature, folder
Opportunity, folder
Participant, folder
PlaybackTheme, folder
Playlist, folder
Scorecard, folder
Webhook, folder
Account.php, class
Activity.php, class
Address.php, class
AiPrompt.php, class
AutomatedReport.php, class
AutomatedReportResult.php, class...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"value":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.09773936,"height":0.040702313},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.017287234,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"bounds":{"left":0.89793885,"top":0.9481245,"width":0.023603724,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.016289894,"height":0.0},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8081782,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"bounds":{"left":0.8234708,"top":0.019952115,"width":0.09208777,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Stop 'AskJiminnyReportActivityServiceTest'","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9494681,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.3793218,"top":0.22426178,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.38896278,"top":0.22266561,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.3962766,"top":0.22266561,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.69514626,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.70511967,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.71476066,"top":0.09896249,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.72207445,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","depth":4,"bounds":{"left":0.42519948,"top":0.09736632,"width":0.30352393,"height":0.90263367},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dev","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dialers","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playlists","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Postmark","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedbacksUpdateEsActivities.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSyncBatchesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RecalculateDealRisksCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RemoveDeleteMarkersCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RemoveExpiredNudgesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RemoveUnusedParticipantSpeechesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ResetElasticSearch.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityCrmProviderIdCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityTypeCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SeedActivities.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"TrackImported.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"WhichWorkerIsWorkingOnWhichJob.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Scheduling, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Kernel.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Contracts, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Domain, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"DTO, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Emails, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Enums, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Exceptions, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Formats, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Guards, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Helpers, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Http, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"AccessTokenProvider, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Controllers, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"API, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Auth, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Internal, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Telephony, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Webhook, folder","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationAppSubscriptions, folder","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivityProviderController.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ActivityTranscriptionController.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"BaseController.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CalendarController.php, final class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"ReportController.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"SoftphoneWebhookController.php, final class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AbstractController.php, abstract class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CommentContextInterface.php, interface","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ConferencesOptInOutController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Controller.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ExportController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FrontendController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FrontendControllerTrait.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeocodingController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"HealthCheckController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"LiveCoachController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MissingTeamController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"MobileController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"NotificationController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"NotificationProviderController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaylistController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PusherController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SlackController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SupportController.php, final class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"TeamSetupController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"UserAutomatedReportsController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"WelcomeController.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Middleware, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Requests, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Resources, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Responses, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Serializers, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Transformers, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Kernel.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"PlaylistTrackResourceTrait.php","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ValidateCrmConnectionRequiredTrait.php","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Integrations, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Interactions, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Jobs, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Listeners, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Activities, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Authentication, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReports, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Calendars, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"BootstrapIntegrationApp.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ImportActivityTypes.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ImportMetadata.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"InitProfiles.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"LayoutModifiedListener.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"LayoutUpdatedListener.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RematchActivityOnCrmObjectDetach.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RemoteCrmRecordDeletedListener.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ResolveOwner.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileLeads.php","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileOpportunities.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Groups, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Import, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Mailbox, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Nudges, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Opportunities, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Teams, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Users, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Webhook, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":".gitkeep","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"ChangeLogContextCorrelationId.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Mail, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Models, folder","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Ai, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Calendar, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Connection, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Contracts, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"BusinessProcess.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Configuration.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ContactRole.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Field.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FieldData.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"FieldValue.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Layout.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"LayoutEntity.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Log.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Profile.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"RecordType.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"SyncBatch.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Feature, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Opportunity, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Participant, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackTheme, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Scorecard, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Webhook, folder","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Account.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activity.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Address.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AiPrompt.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReport.php, class","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportResult.php, class","depth":9,"role_description":"text"}]...
|
2042942877183974646
|
366189393009382805
|
visual_change
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
Stop 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
CoachingFeedbacksUpdateEsActivities.php, class
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IterateUsersCommand.php, abstract class
JiminnyCacheClearCommand.php, class
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php, class
RemoveExpiredNudgesCommand.php, class
RemoveUnusedParticipantSpeechesCommand.php, class
ResetElasticSearch.php, class
RestoreActivityCrmProviderIdCommand.php, class
RestoreActivityTypeCommand.php, class
SeedActivities.php, class
SyncActivity.php, class
TrackImported.php, class
UpdateActivitiesAverageScoreExcludingFeedbacksNotSetVisibleToAll.php, class
WhichWorkerIsWorkingOnWhichJob.php, class
Scheduling, folder
Kernel.php, class
Contracts, folder
Domain, folder
DTO, folder
Emails, folder
Enums, folder
Events, folder
Exceptions, folder
FFMpeg, folder
Formats, folder
Guards, folder
Helpers, folder
Http, folder
AccessTokenProvider, folder
Controllers, folder
API, folder
Auth, folder
CustomerApi, folder
Internal, folder
Kiosk, folder
Settings, folder
Telephony, folder
Webhook, folder
Hubspot, folder
IntegrationAppSubscriptions, folder
ActivityProviderController.php, class
ActivityTranscriptionController.php, class
BaseController.php, class
CalendarController.php, final class
ReportController.php, class
SoftphoneWebhookController.php, final class
AbstractController.php, abstract class
CommentContextInterface.php, interface
ConferencesOptInOutController.php, class
Controller.php, class
ExportController.php, final class
FrontendController.php, class
FrontendControllerTrait.php
GeocodingController.php, final class
HealthCheckController.php, class
LiveCoachController.php, final class
MissingTeamController.php, class
MobileController.php, class
NotificationController.php, class
NotificationProviderController.php, class
PlaybackController.php, final class
PlaylistController.php, final class
PusherController.php, class
SlackController.php, final class
SupportController.php, final class
TeamSetupController.php, class
UserAutomatedReportsController.php, class
WelcomeController.php, class
Middleware, folder
Requests, folder
Resources, folder
Responses, folder
Serializers, folder
Transformers, folder
Kernel.php, class
PlaylistTrackResourceTrait.php
ValidateCrmConnectionRequiredTrait.php
Integrations, folder
Interactions, folder
Jobs, folder
Listeners, folder
Activities, folder
Authentication, folder
AutomatedReports, folder
Calendars, folder
Crm, folder
BootstrapIntegrationApp.php
ImportActivityTypes.php, class
ImportMetadata.php, class
InitProfiles.php, class
LayoutModifiedListener.php, class
LayoutUpdatedListener.php, class
RematchActivityOnCrmObjectDetach.php
RemoteCrmRecordDeletedListener.php, class
ResolveOwner.php, class
SyncOpportunity.php, class
SyncProfileLeads.php
SyncProfileOpportunities.php, class
DealRisks, folder
ElasticSearch, folder
Groups, folder
Import, folder
Mailbox, folder
Nudges, folder
Opportunities, folder
Playbooks, folder
Playlists, folder
Teams, folder
Transcription, folder
Users, folder
Webhook, folder
.gitkeep
ChangeLogContextCorrelationId.php, class
Mail, folder
Models, folder
Activity, folder
Ai, folder
AskAnything, folder
Calendar, folder
Connection, folder
Contracts, folder
Crm, folder
BusinessProcess.php, class
Configuration.php, class
ContactRole.php, class
Field.php, class
FieldData.php, class
FieldValue.php, class
Layout.php, class
LayoutEntity.php, class
Log.php, class
Profile.php, class
RecordType.php, class
SyncBatch.php, class
ElasticSearch, folder
Feature, folder
Opportunity, folder
Participant, folder
PlaybackTheme, folder
Playlist, folder
Scorecard, folder
Webhook, folder
Account.php, class
Activity.php, class
Address.php, class
AiPrompt.php, class
AutomatedReport.php, class
AutomatedReportResult.php, class...
|
55697
|
|
55697
|
1200
|
48
|
2026-04-20T10:01:48.230700+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776679308230_m2.jpg...
|
PhpStorm
|
faVsco.js – AskJiminnyReportActivityServiceTest.ph faVsco.js – AskJiminnyReportActivityServiceTest.php...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
CoachingFeedbacksUpdateEsActivities.php, class
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"value":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.09773936,"height":0.040702313},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.017287234,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"bounds":{"left":0.89793885,"top":0.9481245,"width":0.023603724,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.016289894,"height":0.0},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.7972075,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"bounds":{"left":0.8125,"top":0.019952115,"width":0.10305851,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.3793218,"top":0.22426178,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.38896278,"top":0.22266561,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.3962766,"top":0.22266561,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.69514626,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.70511967,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.71476066,"top":0.09896249,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.72207445,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","depth":4,"bounds":{"left":0.42519948,"top":0.09736632,"width":0.30352393,"height":0.90263367},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Commands","depth":9,"role_description":"text"},{"role":"AXStaticText","text":"Activities","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dev","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Dialers","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Playlists","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Postmark","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Reports","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedbacksUpdateEsActivities.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php, class","depth":10,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php, final class","depth":10,"role_description":"text"}]...
|
4059052151934792368
|
382932895125673363
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration
Console
Commands
Activities
Analytics
Calendars
Crm
DealInsights
Dev
Dialers
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes
Migrate
PlaybackThemes
Playbooks
Playlists
Postmark
ProphetAi
Reports
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
CoachingFeedbacksUpdateEsActivities.php, class
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class...
|
NULL
|
|
55694
|
1200
|
47
|
2026-04-20T10:01:47.134494+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776679307134_m2.jpg...
|
PhpStorm
|
faVsco.js – AskJiminnyReportActivityServiceTest.ph faVsco.js – AskJiminnyReportActivityServiceTest.php...
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.11037234,"height":0.040702313},"value":"Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.9018356,"width":0.09773936,"height":0.040702313},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Rollback","depth":2,"bounds":{"left":0.8753325,"top":0.9481245,"width":0.017287234,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"bounds":{"left":0.89793885,"top":0.9481245,"width":0.023603724,"height":0.013567438},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"More","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.016289894,"height":0.0},"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.7972075,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"RequestGenerateAskJiminnyReportJobTest","depth":6,"bounds":{"left":0.8125,"top":0.019952115,"width":0.10305851,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'RequestGenerateAskJiminnyReportJobTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.3793218,"top":0.22426178,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.38896278,"top":0.22266561,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.3962766,"top":0.22266561,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","depth":4,"value":"<?php\n\nnamespace Jiminny\\Listeners\\Crm;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Events\\Playbooks\\PlaybookCreated;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Repositories\\PlaybookCategoryRepository;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Jiminny\\Services\\ResolveTeamCrmConnection;\nuse stdClass;\n\nclass ImportActivityTypes implements ShouldQueue\n{\n /**\n * Create the event listener.\n */\n public function __construct(\n private readonly ResolveTeamCrmConnection $crmResolver,\n private readonly FieldRepository $fieldRepository,\n private readonly PlaybookCategoryRepository $repository,\n ) {\n // nothing\n }\n\n /**\n * Import the standard Event/Task Type picklist options from the CRM.\n */\n public function handle(PlaybookCreated $event): void\n {\n $playbook = $event->playbook;\n\n // Don't run if somehow we already have categories.\n if ($playbook->getCategories()->isNotEmpty()) {\n return;\n }\n\n $crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());\n $crmService->syncField($playbook->getActivityField());\n\n $values = $crmService->importPicklistValues($playbook->getActivityField());\n\n if (empty($values)) {\n $values = $this->fetchActivityFieldValues($playbook->getActivityField());\n }\n\n /** @var stdClass{label: string} $value */\n foreach ($values as $value) {\n $data = [\n 'name' => $value->label,\n 'enabled' => true,\n 'type' => PlaybookCategory::TYPE_ALL,\n ];\n\n if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;\n }\n\n if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {\n $data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;\n }\n\n $this->repository->create($playbook, $data);\n }\n }\n\n private function fetchActivityFieldValues(Field $field): Collection\n {\n /** @var Collection<FieldValue> */\n return $this->fieldRepository->getPicklistValues($field);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.69514626,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.70511967,"top":0.10055866,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.71476066,"top":0.09896249,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.72207445,"top":0.09896249,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","depth":4,"bounds":{"left":0.42519948,"top":0.09736632,"width":0.30352393,"height":0.90263367},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\CarbonImmutable;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityActualDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\ActivityUpdatedDate;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinition\\DealInsights\\ClosingPeriodFilter;\nuse Jiminny\\Component\\ActivitySearch\\FilterDefinitionCollection;\nuse Jiminny\\Component\\ActivitySearch\\Service\\ActivitySearch;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\Activity\\SearchFilter;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\ElasticActivityRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AskJiminnyReportActivityService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\VO\\Repository\\OnDemandActivitySearch\\Criteria;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AskJiminnyReportActivityServiceTest extends TestCase\n{\n private ActivitySearch&MockObject $activitySearch;\n private ElasticActivityRepository&MockObject $elasticRepository;\n private LoggerInterface&MockObject $logger;\n private AskJiminnyReportActivityService $service;\n\n protected function setUp(): void\n {\n $this->activitySearch = $this->createMock(ActivitySearch::class);\n $this->elasticRepository = $this->createMock(ElasticActivityRepository::class);\n $this->logger = $this->createMock(LoggerInterface::class);\n\n $this->service = new AskJiminnyReportActivityService(\n $this->activitySearch,\n $this->elasticRepository,\n $this->logger,\n );\n }\n\n private function makeFilter(string $key, ?string $value): SearchFilter&MockObject\n {\n $filter = $this->createMock(SearchFilter::class);\n $filter->method('getFilterProperty')->willReturn($key);\n $filter->method('getFilterValue')->willReturn($value);\n\n return $filter;\n }\n\n private function makeUser(): User&MockObject\n {\n $tz = new \\DateTimeZone('UTC');\n $user = $this->createMock(User::class);\n $user->method('getTimezone')->willReturn($tz);\n $user->method('getId')->willReturn(1);\n $user->method('getUuid')->willReturn('user-uuid');\n\n return $user;\n }\n\n private function makeSavedSearch(array $filters): Search&MockObject\n {\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(42);\n $savedSearch->method('getFilters')->willReturn(new \\Illuminate\\Support\\LazyCollection($filters));\n\n return $savedSearch;\n }\n\n public function testGetActivityIdsForSavedSearchReturnsIds(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->expects($this->once())\n ->method('getArrayFilterKeys')\n ->with($user)\n ->willReturn([]);\n\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n\n $this->elasticRepository->expects($this->once())\n ->method('onDemandSearchIdsOnly')\n ->willReturn(['id-1', 'id-2', 'id-3']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with('[AskJiminnyReport] Fetched activity IDs for saved search');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1', 'id-2', 'id-3'], $result);\n }\n\n public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $this->logger->expects($this->once())->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEmpty($result);\n }\n\n public function testGetActivityIdsFiltersOutDateFilters(): void\n {\n $user = $this->makeUser();\n\n $nonDateFilter = $this->makeFilter('owner_id', '123');\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');\n $updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');\n $updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');\n\n $savedSearch = $this->makeSavedSearch([\n $nonDateFilter,\n $startDateFilter,\n $endDateFilter,\n $updatedFromFilter,\n $updatedToFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n }\n\n public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void\n {\n $user = $this->makeUser();\n\n $closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');\n $closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');\n $regularFilter = $this->makeFilter('rep_id', '99');\n\n $savedSearch = $this->makeSavedSearch([\n $closingStartFilter,\n $closingEndFilter,\n $regularFilter,\n ]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesArrayFilters(): void\n {\n $user = $this->makeUser();\n\n $filter1 = $this->makeFilter('outcome', 'positive');\n $filter2 = $this->makeFilter('outcome', 'negative');\n\n $savedSearch = $this->makeSavedSearch([$filter1, $filter2]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-1'], $result);\n }\n\n public function testGetActivityIdsHandlesScalarFilters(): void\n {\n $user = $this->makeUser();\n\n $filter = $this->makeFilter('direction', 'inbound');\n $savedSearch = $this->makeSavedSearch([$filter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);\n $this->logger->method('info');\n\n $result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertEquals(['id-5'], $result);\n }\n\n public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n $this->logger->method('info');\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertFalse($capturedCriteria->isFirstRequest());\n }\n\n public function testGetActivityIdsLogsWithCorrectContext(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);\n\n $this->logger->expects($this->once())\n ->method('info')\n ->with(\n '[AskJiminnyReport] Fetched activity IDs for saved search',\n $this->callback(fn ($context) => $context['saved_search_id'] === 42\n && $context['user_id'] === 1\n && $context['activity_count'] === 2)\n );\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user);\n }\n\n public static function frequencyDateRangeProvider(): array\n {\n return [\n 'daily' => [\n AutomatedReportsService::FREQUENCY_DAILY,\n '2025-06-15 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'weekly' => [\n AutomatedReportsService::FREQUENCY_WEEKLY,\n '2025-06-09 00:00:00',\n '2025-06-15 23:59:59',\n ],\n 'monthly' => [\n AutomatedReportsService::FREQUENCY_MONTHLY,\n '2025-05-01 00:00:00',\n '2025-05-31 23:59:59',\n ],\n 'quarterly' => [\n AutomatedReportsService::FREQUENCY_QUARTERLY,\n '2025-01-01 00:00:00',\n '2025-03-31 23:59:59',\n ],\n ];\n }\n\n /**\n * @dataProvider frequencyDateRangeProvider\n */\n public function testGetActivityIdsInjectsDateRangeForFrequency(\n string $frequency,\n string $expectedStartDate,\n string $expectedEndDate,\n ): void {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n\n public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void\n {\n $user = $this->makeUser();\n $savedSearch = $this->makeSavedSearch([]);\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertNull($capturedCriteria->getStartDate());\n $this->assertNull($capturedCriteria->getEndDate());\n }\n\n public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void\n {\n CarbonImmutable::setTestNow('2025-06-16 12:00:00');\n\n try {\n $user = $this->makeUser();\n\n $startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');\n $endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');\n $savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);\n\n $filterSet = $this->createMock(FilterDefinitionCollection::class);\n\n $this->activitySearch->method('getArrayFilterKeys')->willReturn([]);\n $this->logger->method('info');\n $this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);\n\n $capturedCriteria = null;\n $this->activitySearch->expects($this->once())\n ->method('getOnDemandPageFilterSet')\n ->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {\n $capturedCriteria = $criteria;\n\n return $filterSet;\n });\n\n $this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);\n\n $this->assertNotNull($capturedCriteria);\n $this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));\n $this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));\n } finally {\n CarbonImmutable::setTestNow();\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"role_description":"text"}]...
|
-1984786027757248056
|
941379042777961875
|
visual_change
|
accessibility
|
NULL
|
Workspace associated with branch 'JY-18909-aut Workspace associated with branch 'JY-18909-automated-reports-ask-jiminny' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
2
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Listeners\Crm;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Jiminny\Events\Playbooks\PlaybookCreated;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Jiminny\Services\ResolveTeamCrmConnection;
use stdClass;
class ImportActivityTypes implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct(
private readonly ResolveTeamCrmConnection $crmResolver,
private readonly FieldRepository $fieldRepository,
private readonly PlaybookCategoryRepository $repository,
) {
// nothing
}
/**
* Import the standard Event/Task Type picklist options from the CRM.
*/
public function handle(PlaybookCreated $event): void
{
$playbook = $event->playbook;
// Don't run if somehow we already have categories.
if ($playbook->getCategories()->isNotEmpty()) {
return;
}
$crmService = $this->crmResolver->resolveForTeam($playbook->getTeam());
$crmService->syncField($playbook->getActivityField());
$values = $crmService->importPicklistValues($playbook->getActivityField());
if (empty($values)) {
$values = $this->fetchActivityFieldValues($playbook->getActivityField());
}
/** @var stdClass{label: string} $value */
foreach ($values as $value) {
$data = [
'name' => $value->label,
'enabled' => true,
'type' => PlaybookCategory::TYPE_ALL,
];
if (Str::contains(strtolower($value->label), ['sms sent', 'sms out', 'text in'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_OUTBOUND;
}
if (Str::contains(strtolower($value->label), ['sms received', 'sms in', 'text out'])) {
$data['type'] = PlaybookCategory::TYPE_SMS_INBOUND;
}
$this->repository->create($playbook, $data);
}
}
private function fetchActivityFieldValues(Field $field): Collection
{
/** @var Collection<FieldValue> */
return $this->fieldRepository->getPicklistValues($field);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityActualDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\ActivityUpdatedDate;
use Jiminny\Component\ActivitySearch\FilterDefinition\DealInsights\ClosingPeriodFilter;
use Jiminny\Component\ActivitySearch\FilterDefinitionCollection;
use Jiminny\Component\ActivitySearch\Service\ActivitySearch;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\Activity\SearchFilter;
use Jiminny\Models\User;
use Jiminny\Repositories\ElasticActivityRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\VO\Repository\OnDemandActivitySearch\Criteria;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AskJiminnyReportActivityServiceTest extends TestCase
{
private ActivitySearch&MockObject $activitySearch;
private ElasticActivityRepository&MockObject $elasticRepository;
private LoggerInterface&MockObject $logger;
private AskJiminnyReportActivityService $service;
protected function setUp(): void
{
$this->activitySearch = $this->createMock(ActivitySearch::class);
$this->elasticRepository = $this->createMock(ElasticActivityRepository::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new AskJiminnyReportActivityService(
$this->activitySearch,
$this->elasticRepository,
$this->logger,
);
}
private function makeFilter(string $key, ?string $value): SearchFilter&MockObject
{
$filter = $this->createMock(SearchFilter::class);
$filter->method('getFilterProperty')->willReturn($key);
$filter->method('getFilterValue')->willReturn($value);
return $filter;
}
private function makeUser(): User&MockObject
{
$tz = new \DateTimeZone('UTC');
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn($tz);
$user->method('getId')->willReturn(1);
$user->method('getUuid')->willReturn('user-uuid');
return $user;
}
private function makeSavedSearch(array $filters): Search&MockObject
{
$savedSearch = $this->createMock(Search::class);
$savedSearch->method('getId')->willReturn(42);
$savedSearch->method('getFilters')->willReturn(new \Illuminate\Support\LazyCollection($filters));
return $savedSearch;
}
public function testGetActivityIdsForSavedSearchReturnsIds(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->expects($this->once())
->method('getArrayFilterKeys')
->with($user)
->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->expects($this->once())
->method('onDemandSearchIdsOnly')
->willReturn(['id-1', 'id-2', 'id-3']);
$this->logger->expects($this->once())
->method('info')
->with('[AskJiminnyReport] Fetched activity IDs for saved search');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1', 'id-2', 'id-3'], $result);
}
public function testGetActivityIdsForSavedSearchReturnsEmptyWhenNoResults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->expects($this->once())->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEmpty($result);
}
public function testGetActivityIdsFiltersOutDateFilters(): void
{
$user = $this->makeUser();
$nonDateFilter = $this->makeFilter('owner_id', '123');
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2025-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2025-01-31 23:59:59');
$updatedFromFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_FROM, '2025-01-01 00:00:00');
$updatedToFilter = $this->makeFilter(ActivityUpdatedDate::PARAM_UPDATED_TO, '2025-01-31 23:59:59');
$savedSearch = $this->makeSavedSearch([
$nonDateFilter,
$startDateFilter,
$endDateFilter,
$updatedFromFilter,
$updatedToFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
}
public function testGetActivityIdsFiltersOutClosingPeriodDateFilters(): void
{
$user = $this->makeUser();
$closingStartFilter = $this->makeFilter(ClosingPeriodFilter::KEY_START_DATE, '2025-01-01');
$closingEndFilter = $this->makeFilter(ClosingPeriodFilter::KEY_END_DATE, '2025-03-31');
$regularFilter = $this->makeFilter('rep_id', '99');
$savedSearch = $this->makeSavedSearch([
$closingStartFilter,
$closingEndFilter,
$regularFilter,
]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesArrayFilters(): void
{
$user = $this->makeUser();
$filter1 = $this->makeFilter('outcome', 'positive');
$filter2 = $this->makeFilter('outcome', 'negative');
$savedSearch = $this->makeSavedSearch([$filter1, $filter2]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn(['outcome']);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-1']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-1'], $result);
}
public function testGetActivityIdsHandlesScalarFilters(): void
{
$user = $this->makeUser();
$filter = $this->makeFilter('direction', 'inbound');
$savedSearch = $this->makeSavedSearch([$filter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['id-5']);
$this->logger->method('info');
$result = $this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertEquals(['id-5'], $result);
}
public function testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$this->logger->method('info');
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
$this->assertNotNull($capturedCriteria);
$this->assertFalse($capturedCriteria->isFirstRequest());
}
public function testGetActivityIdsLogsWithCorrectContext(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->activitySearch->method('getOnDemandPageFilterSet')->willReturn($filterSet);
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn(['a', 'b']);
$this->logger->expects($this->once())
->method('info')
->with(
'[AskJiminnyReport] Fetched activity IDs for saved search',
$this->callback(fn ($context) => $context['saved_search_id'] === 42
&& $context['user_id'] === 1
&& $context['activity_count'] === 2)
);
$this->service->getActivityIdsForSavedSearch($savedSearch, $user);
}
public static function frequencyDateRangeProvider(): array
{
return [
'daily' => [
AutomatedReportsService::FREQUENCY_DAILY,
'2025-06-15 00:00:00',
'2025-06-15 23:59:59',
],
'weekly' => [
AutomatedReportsService::FREQUENCY_WEEKLY,
'2025-06-09 00:00:00',
'2025-06-15 23:59:59',
],
'monthly' => [
AutomatedReportsService::FREQUENCY_MONTHLY,
'2025-05-01 00:00:00',
'2025-05-31 23:59:59',
],
'quarterly' => [
AutomatedReportsService::FREQUENCY_QUARTERLY,
'2025-01-01 00:00:00',
'2025-03-31 23:59:59',
],
];
}
/**
* @dataProvider frequencyDateRangeProvider
*/
public function testGetActivityIdsInjectsDateRangeForFrequency(
string $frequency,
string $expectedStartDate,
string $expectedEndDate,
): void {
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, $frequency);
$this->assertNotNull($capturedCriteria);
$this->assertSame($expectedStartDate, $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame($expectedEndDate, $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
public function testGetActivityIdsWithNullFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, null);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsWithUnknownFrequencyDoesNotInjectDates(): void
{
$user = $this->makeUser();
$savedSearch = $this->makeSavedSearch([]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_ONE_OFF);
$this->assertNotNull($capturedCriteria);
$this->assertNull($capturedCriteria->getStartDate());
$this->assertNull($capturedCriteria->getEndDate());
}
public function testGetActivityIdsFrequencyDateRangeOverridesSavedSearchDateFilters(): void
{
CarbonImmutable::setTestNow('2025-06-16 12:00:00');
try {
$user = $this->makeUser();
$startDateFilter = $this->makeFilter(ActivityActualDate::PARAM_START_DATE, '2024-01-01 00:00:00');
$endDateFilter = $this->makeFilter(ActivityActualDate::PARAM_END_DATE, '2024-12-31 23:59:59');
$savedSearch = $this->makeSavedSearch([$startDateFilter, $endDateFilter]);
$filterSet = $this->createMock(FilterDefinitionCollection::class);
$this->activitySearch->method('getArrayFilterKeys')->willReturn([]);
$this->logger->method('info');
$this->elasticRepository->method('onDemandSearchIdsOnly')->willReturn([]);
$capturedCriteria = null;
$this->activitySearch->expects($this->once())
->method('getOnDemandPageFilterSet')
->willReturnCallback(function (Criteria $criteria) use ($filterSet, &$capturedCriteria) {
$capturedCriteria = $criteria;
return $filterSet;
});
$this->service->getActivityIdsForSavedSearch($savedSearch, $user, AutomatedReportsService::FREQUENCY_DAILY);
$this->assertNotNull($capturedCriteria);
$this->assertSame('2025-06-15 00:00:00', $capturedCriteria->getStartDate()->format('Y-m-d H:i:s'));
$this->assertSame('2025-06-15 23:59:59', $capturedCriteria->getEndDate()->format('Y-m-d H:i:s'));
} finally {
CarbonImmutable::setTestNow();
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf
app, sources root
Actions
Component
Configuration...
|
55693
|
|
65105
|
1443
|
18
|
2026-04-21T11:59:47.342025+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776772787342_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepository.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Unable to find diagrams for the selected elements
Unable to find diagrams for the selected elements
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
16
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active Ask Jiminny reports whose expiry date has passed.
*
* @return Collection<AutomatedReport>
*/
public function getExpiredActiveAskJiminnyReports(): Collection
{
return AutomatedReport::where('status', true)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->whereNotNull('expires_at')
->where('expires_at', '<', now()->toDateString())
->get();
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function findLatestDefaultOrFailedResult(AutomatedReport $report): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('report_id', $report->getId())
->whereIn('status', [AutomatedReportResult::STATUS_DEFAULT, AutomatedReportResult::STATUS_FAILED])
->latest()
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set ask_anything_prompt_id = NULL where id = 69;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Unable to find diagrams for the selected elements","depth":2,"value":"Unable to find diagrams for the selected elements","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":3,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active Ask Jiminny reports whose expiry date has passed.\n *\n * @return Collection<AutomatedReport>\n */\n public function getExpiredActiveAskJiminnyReports(): Collection\n {\n return AutomatedReport::where('status', true)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->whereNotNull('expires_at')\n ->where('expires_at', '<', now()->toDateString())\n ->get();\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function findLatestDefaultOrFailedResult(AutomatedReport $report): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('report_id', $report->getId())\n ->whereIn('status', [AutomatedReportResult::STATUS_DEFAULT, AutomatedReportResult::STATUS_FAILED])\n ->latest()\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Repositories;\n\nuse Carbon\\CarbonImmutable;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Pagination\\LengthAwarePaginator;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\n\nclass AutomatedReportsRepository\n{\n /**\n * Create a new automated report\n *\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function create(array $data): AutomatedReport\n {\n return AutomatedReport::create($data);\n }\n\n /**\n * Find an automated report by UUID\n *\n * @param string $uuid\n *\n * @return AutomatedReport|null\n */\n public function findByUuid(string $uuid): ?AutomatedReport\n {\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();\n }\n\n public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport\n {\n if (is_numeric($idOrUuid)) {\n return AutomatedReport::find((int) $idOrUuid);\n }\n\n return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();\n }\n\n /**\n * Retrieve all standard (non-Ask Jiminny) automated reports.\n *\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAllStandardReports(\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->get();\n }\n\n /**\n * Retrieve all Ask Jiminny reports created by the given user.\n *\n * @param User $user The user whose reports to retrieve.\n * @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.\n * @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.\n *\n * @return Collection<AutomatedReport>\n */\n public function getAskJiminnyReportsByUser(\n User $user,\n string $sortColumn = 'created_at',\n string $sortDirection = 'desc'\n ): Collection {\n return $this->buildSortedQuery($sortColumn, $sortDirection)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->where('created_by', $user->getId())\n ->get();\n }\n\n private function buildSortedQuery(string $sortColumn, string $sortDirection): \\Illuminate\\Database\\Eloquent\\Builder\n {\n $allowedColumns = ['created_by', 'created_at'];\n if (! in_array($sortColumn, $allowedColumns)) {\n $sortColumn = 'created_at';\n }\n\n $sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';\n\n $query = AutomatedReport::query()->with(['creator', 'team']);\n\n if ($sortColumn === 'created_by') {\n $query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')\n ->orderByRaw(\"users.name COLLATE utf8mb4_unicode_ci {$sortDirection}\")\n ->select('automated_reports.*');\n } else {\n $query->orderBy($sortColumn, $sortDirection);\n }\n\n return $query;\n }\n\n /**\n * Get all active Ask Jiminny reports whose expiry date has passed.\n *\n * @return Collection<AutomatedReport>\n */\n public function getExpiredActiveAskJiminnyReports(): Collection\n {\n return AutomatedReport::where('status', true)\n ->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)\n ->whereNotNull('expires_at')\n ->where('expires_at', '<', now()->toDateString())\n ->get();\n }\n\n /**\n * Get all active and enabled reports with active teams for the specified frequency.\n *\n * @param string $frequency\n *\n * @return Collection<AutomatedReport>\n */\n public function getActiveReportsByFrequency(string $frequency): Collection\n {\n return AutomatedReport::where('automated_reports.status', true)\n ->where('automated_reports.frequency', $frequency)\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->where('teams.status', Team::STATUS_ACTIVE)\n ->where(function ($query) {\n $query->whereNull('automated_reports.expires_at')\n ->orWhere('automated_reports.expires_at', '>=', now()->toDateString());\n })\n ->select('automated_reports.*')\n ->get();\n }\n\n /**\n * Update an automated report\n *\n * @param AutomatedReport $report\n * @param array $data\n *\n * @return AutomatedReport\n */\n public function update(AutomatedReport $report, array $data): AutomatedReport\n {\n $report->update($data);\n\n return $report;\n }\n\n /**\n * Create a new automated report result.\n *\n * @param array $data The data to create the automated report result with.\n *\n * @return AutomatedReportResult The newly created automated report result.\n */\n public function createResult(array $data): AutomatedReportResult\n {\n return AutomatedReportResult::create($data);\n }\n\n /**\n * Find an automated report result by UUID.\n *\n * @param string $uuid The UUID to find the automated report result with.\n *\n * @return AutomatedReportResult|null The automated report result if found, otherwise null.\n */\n public function findResultByUuid(string $uuid): ?AutomatedReportResult\n {\n return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();\n }\n\n public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('uuid', AutomatedReportResult::toOptimized($uuid))\n ->whereHas('report', static function ($query) use ($user): void {\n $query->where('team_id', $user->getTeamId())\n ->where('created_by', $user->getId());\n })\n ->first();\n }\n\n public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('parent_id', $result->getId())\n ->where('media_type', $type)\n ->first();\n }\n\n public function findLatestDefaultOrFailedResult(AutomatedReport $report): ?AutomatedReportResult\n {\n return AutomatedReportResult::query()\n ->where('report_id', $report->getId())\n ->whereIn('status', [AutomatedReportResult::STATUS_DEFAULT, AutomatedReportResult::STATUS_FAILED])\n ->latest()\n ->first();\n }\n\n public function getGeneratedNotSentResults(): Collection\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNull('sent_at')\n ->where('status', AutomatedReportResult::STATUS_GENERATED)\n ->whereHas('report')\n ->with('report')\n ->get();\n }\n\n public function getPaginatedUserReports(\n User $user,\n ReportSort $sort,\n ReportSortDirection $sortDirection,\n int $resultsPerPage,\n int $page,\n ?Carbon $fromDate,\n ?Carbon $toDate,\n array $teamIds,\n array $reportTypes,\n ?string $name,\n ): LengthAwarePaginator {\n $query = AutomatedReportResult::query()\n ->whereNotNull('automated_report_results.generated_at')\n ->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')\n ->where('automated_reports.team_id', $user->getTeamId())\n ->whereJsonContains('automated_reports.recipients->users', $user->getId())\n ->orderByRaw(\"$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}\")\n ->select('automated_report_results.*')\n ->with('report.team');\n\n if ($fromDate !== null && $toDate !== null) {\n $query->whereBetween('generated_at', [$fromDate, $toDate]);\n }\n\n if (! empty($teamIds)) {\n $query->where(function ($q) use ($teamIds) {\n foreach ($teamIds as $id) {\n $q->orWhereJsonContains('automated_reports.groups', $id);\n }\n });\n }\n\n if (! empty($reportTypes)) {\n $query->whereIn('automated_reports.type', $reportTypes);\n }\n\n if (! empty($name)) {\n $query->whereLike('name', \"%$name%\");\n }\n\n return $query->paginate($resultsPerPage, ['*'], 'page', $page);\n }\n\n public function countUserReports(User $user): int\n {\n return AutomatedReportResult::query()\n ->whereNotNull('generated_at')\n ->whereNotNull('sent_at')\n ->whereHas('report', function ($q) use ($user) {\n $q->where('team_id', $user->getTeamId())\n ->whereJsonContains('recipients->users', $user->getId());\n })\n ->count();\n }\n\n /**\n * Get report IDs for a specific team\n *\n * @param Team $team\n *\n * @return \\Illuminate\\Support\\Collection\n */\n public function getReportIdsByTeam(Team $team): \\Illuminate\\Support\\Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->pluck('id');\n }\n\n /**\n * Get all reports for a specific team\n *\n * @param Team $team\n *\n * @return Collection\n */\n public function getReportsByTeam(Team $team): Collection\n {\n return AutomatedReport::where('team_id', $team->getId())->get();\n }\n\n /**\n * Get all report results for a specific report\n *\n * @param AutomatedReport $report\n *\n * @return Collection\n */\n public function getResultsByReport(AutomatedReport $report): Collection\n {\n return $this->getResultsByReportQuery($report)->get();\n }\n\n public function getResultsByReportQuery(AutomatedReport $report): Builder\n {\n return AutomatedReportResult::where('report_id', $report->getId());\n }\n\n public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder\n {\n $reportIds = $this->getReportIdsByTeam($team);\n\n return AutomatedReportResult::query()->whereIn('report_id', $reportIds)\n ->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);\n }\n\n /**\n * @param int|null $teamId Optional team ID to filter results\n *\n * @return \\Illuminate\\Support\\Collection<int, int> Collection of team IDs\n */\n public function getTeamIdsWithReportsResults(?int $teamId = null): \\Illuminate\\Support\\Collection\n {\n $query = DB::table('automated_reports')\n ->join('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->select('teams.id')\n ->distinct();\n\n if ($teamId !== null) {\n $query->where('teams.id', $teamId);\n }\n\n return $query->pluck('teams.id');\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set ask_anything_prompt_id = NULL where id = 69;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","depth":4,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set ask_anything_prompt_id = NULL where id = 69;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
4755984369087833973
|
-2393068234936543675
|
visual_change
|
accessibility
|
NULL
|
Unable to find diagrams for the selected elements
Unable to find diagrams for the selected elements
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
16
6
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
class AutomatedReportsRepository
{
/**
* Create a new automated report
*
* @param array $data
*
* @return AutomatedReport
*/
public function create(array $data): AutomatedReport
{
return AutomatedReport::create($data);
}
/**
* Find an automated report by UUID
*
* @param string $uuid
*
* @return AutomatedReport|null
*/
public function findByUuid(string $uuid): ?AutomatedReport
{
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($uuid))->first();
}
public function findByIdOrUuid(string $idOrUuid): ?AutomatedReport
{
if (is_numeric($idOrUuid)) {
return AutomatedReport::find((int) $idOrUuid);
}
return AutomatedReport::where('uuid', AutomatedReport::toOptimized($idOrUuid))->first();
}
/**
* Retrieve all standard (non-Ask Jiminny) automated reports.
*
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAllStandardReports(
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->whereNot('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->get();
}
/**
* Retrieve all Ask Jiminny reports created by the given user.
*
* @param User $user The user whose reports to retrieve.
* @param string $sortColumn The column to sort by. Allowed values: 'created_by', 'created_at'. Defaults to 'created_at'.
* @param string $sortDirection The sort direction. Allowed values: 'asc', 'desc'. Defaults to 'desc'.
*
* @return Collection<AutomatedReport>
*/
public function getAskJiminnyReportsByUser(
User $user,
string $sortColumn = 'created_at',
string $sortDirection = 'desc'
): Collection {
return $this->buildSortedQuery($sortColumn, $sortDirection)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->where('created_by', $user->getId())
->get();
}
private function buildSortedQuery(string $sortColumn, string $sortDirection): \Illuminate\Database\Eloquent\Builder
{
$allowedColumns = ['created_by', 'created_at'];
if (! in_array($sortColumn, $allowedColumns)) {
$sortColumn = 'created_at';
}
$sortDirection = strtolower($sortDirection) === 'asc' ? 'asc' : 'desc';
$query = AutomatedReport::query()->with(['creator', 'team']);
if ($sortColumn === 'created_by') {
$query->leftJoin('users', 'users.id', '=', 'automated_reports.created_by')
->orderByRaw("users.name COLLATE utf8mb4_unicode_ci {$sortDirection}")
->select('automated_reports.*');
} else {
$query->orderBy($sortColumn, $sortDirection);
}
return $query;
}
/**
* Get all active Ask Jiminny reports whose expiry date has passed.
*
* @return Collection<AutomatedReport>
*/
public function getExpiredActiveAskJiminnyReports(): Collection
{
return AutomatedReport::where('status', true)
->where('type', AutomatedReportsService::TYPE_ASK_JIMINNY)
->whereNotNull('expires_at')
->where('expires_at', '<', now()->toDateString())
->get();
}
/**
* Get all active and enabled reports with active teams for the specified frequency.
*
* @param string $frequency
*
* @return Collection<AutomatedReport>
*/
public function getActiveReportsByFrequency(string $frequency): Collection
{
return AutomatedReport::where('automated_reports.status', true)
->where('automated_reports.frequency', $frequency)
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->where('teams.status', Team::STATUS_ACTIVE)
->where(function ($query) {
$query->whereNull('automated_reports.expires_at')
->orWhere('automated_reports.expires_at', '>=', now()->toDateString());
})
->select('automated_reports.*')
->get();
}
/**
* Update an automated report
*
* @param AutomatedReport $report
* @param array $data
*
* @return AutomatedReport
*/
public function update(AutomatedReport $report, array $data): AutomatedReport
{
$report->update($data);
return $report;
}
/**
* Create a new automated report result.
*
* @param array $data The data to create the automated report result with.
*
* @return AutomatedReportResult The newly created automated report result.
*/
public function createResult(array $data): AutomatedReportResult
{
return AutomatedReportResult::create($data);
}
/**
* Find an automated report result by UUID.
*
* @param string $uuid The UUID to find the automated report result with.
*
* @return AutomatedReportResult|null The automated report result if found, otherwise null.
*/
public function findResultByUuid(string $uuid): ?AutomatedReportResult
{
return AutomatedReportResult::where('uuid', AutomatedReportResult::toOptimized($uuid))->first();
}
public function findResultByUuidForUser(string $uuid, User $user): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('uuid', AutomatedReportResult::toOptimized($uuid))
->whereHas('report', static function ($query) use ($user): void {
$query->where('team_id', $user->getTeamId())
->where('created_by', $user->getId());
})
->first();
}
public function findChildResult(AutomatedReportResult $result, string $type): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('parent_id', $result->getId())
->where('media_type', $type)
->first();
}
public function findLatestDefaultOrFailedResult(AutomatedReport $report): ?AutomatedReportResult
{
return AutomatedReportResult::query()
->where('report_id', $report->getId())
->whereIn('status', [AutomatedReportResult::STATUS_DEFAULT, AutomatedReportResult::STATUS_FAILED])
->latest()
->first();
}
public function getGeneratedNotSentResults(): Collection
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNull('sent_at')
->where('status', AutomatedReportResult::STATUS_GENERATED)
->whereHas('report')
->with('report')
->get();
}
public function getPaginatedUserReports(
User $user,
ReportSort $sort,
ReportSortDirection $sortDirection,
int $resultsPerPage,
int $page,
?Carbon $fromDate,
?Carbon $toDate,
array $teamIds,
array $reportTypes,
?string $name,
): LengthAwarePaginator {
$query = AutomatedReportResult::query()
->whereNotNull('automated_report_results.generated_at')
->join('automated_reports', 'automated_report_results.report_id', '=', 'automated_reports.id')
->where('automated_reports.team_id', $user->getTeamId())
->whereJsonContains('automated_reports.recipients->users', $user->getId())
->orderByRaw("$sort->value COLLATE utf8mb4_unicode_ci {$sortDirection->value}")
->select('automated_report_results.*')
->with('report.team');
if ($fromDate !== null && $toDate !== null) {
$query->whereBetween('generated_at', [$fromDate, $toDate]);
}
if (! empty($teamIds)) {
$query->where(function ($q) use ($teamIds) {
foreach ($teamIds as $id) {
$q->orWhereJsonContains('automated_reports.groups', $id);
}
});
}
if (! empty($reportTypes)) {
$query->whereIn('automated_reports.type', $reportTypes);
}
if (! empty($name)) {
$query->whereLike('name', "%$name%");
}
return $query->paginate($resultsPerPage, ['*'], 'page', $page);
}
public function countUserReports(User $user): int
{
return AutomatedReportResult::query()
->whereNotNull('generated_at')
->whereNotNull('sent_at')
->whereHas('report', function ($q) use ($user) {
$q->where('team_id', $user->getTeamId())
->whereJsonContains('recipients->users', $user->getId());
})
->count();
}
/**
* Get report IDs for a specific team
*
* @param Team $team
*
* @return \Illuminate\Support\Collection
*/
public function getReportIdsByTeam(Team $team): \Illuminate\Support\Collection
{
return AutomatedReport::where('team_id', $team->getId())->pluck('id');
}
/**
* Get all reports for a specific team
*
* @param Team $team
*
* @return Collection
*/
public function getReportsByTeam(Team $team): Collection
{
return AutomatedReport::where('team_id', $team->getId())->get();
}
/**
* Get all report results for a specific report
*
* @param AutomatedReport $report
*
* @return Collection
*/
public function getResultsByReport(AutomatedReport $report): Collection
{
return $this->getResultsByReportQuery($report)->get();
}
public function getResultsByReportQuery(AutomatedReport $report): Builder
{
return AutomatedReportResult::where('report_id', $report->getId());
}
public function getReportResultsQueryForRetention(Team $team, CarbonImmutable $retentionDate): Builder
{
$reportIds = $this->getReportIdsByTeam($team);
return AutomatedReportResult::query()->whereIn('report_id', $reportIds)
->whereRaw('IFNULL(generated_at, created_at) <= ?', [$retentionDate]);
}
/**
* @param int|null $teamId Optional team ID to filter results
*
* @return \Illuminate\Support\Collection<int, int> Collection of team IDs
*/
public function getTeamIdsWithReportsResults(?int $teamId = null): \Illuminate\Support\Collection
{
$query = DB::table('automated_reports')
->join('teams', 'automated_reports.team_id', '=', 'teams.id')
->select('teams.id')
->distinct();
if ($teamId !== null) {
$query->where('teams.id', $teamId);
}
return $query->pluck('teams.id');
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set ask_anything_prompt_id = NULL where id = 69;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
75365
|
1880
|
3
|
2026-04-24T06:21:02.462097+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-24/1777 /Users/lukas/.screenpipe/data/data/2026-04-24/1777011662462_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsServiceTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
The Hunspell plugin has been deprecated. If you The Hunspell plugin has been deprecated. If you're not writing in Hungarian, you can safely uninstall Hunspell without affecting the dictionaries for other languages.
text/html
text/html
text/html
Loading Data Sources…
Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"The Hunspell plugin has been deprecated. If you're not writing in Hungarian, you can safely uninstall Hunspell without affecting the dictionaries for other languages.","depth":3,"bounds":{"left":0.8753325,"top":0.8172386,"width":0.11037234,"height":0.054269753},"value":"The Hunspell plugin has been deprecated. If you're not writing in Hungarian, you can safely uninstall Hunspell without affecting the dictionaries for other languages.","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.8753325,"top":0.8172386,"width":0.095744684,"height":0.054269753},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Loading Data Sources…","depth":2,"bounds":{"left":0.6775266,"top":0.74461293,"width":0.06948138,"height":0.011173184},"role_description":"text"},{"role":"AXStaticText","text":"","depth":2,"bounds":{"left":0.6775266,"top":0.7765363,"width":0.06948138,"height":0.011173184},"role_description":"text"},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.25731382,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#12011 on JY-20157-AJ-report-not-send-notification, menu","depth":5,"bounds":{"left":0.29587767,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #12011 exists for current branch JY-20157-AJ-report-not-send-notification","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8218085,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsServiceTest","depth":6,"bounds":{"left":0.83710104,"top":0.019952115,"width":0.078457445,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Analyzing…","depth":4,"bounds":{"left":0.58477396,"top":0.32322428,"width":0.019946808,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.60638297,"top":0.3216281,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6136968,"top":0.3216281,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n}","depth":4,"bounds":{"left":0.38231382,"top":0.3200319,"width":0.3600399,"height":0.6799681},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Services\\Kiosk\\AutomatedReports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Carbon as IlluminateCarbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\Relations\\HasMany;\nuse Illuminate\\Support\\Facades\\Log;\nuse Illuminate\\Support\\Facades\\Storage;\nuse Jiminny\\Component\\AskAnything\\AskAnythingPromptService;\nuse Jiminny\\Component\\AskAnything\\Dtos\\AskAnythingPromptDto;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Contracts\\Repositories\\PlaybookCategoryRepository;\nuse Jiminny\\Contracts\\Repositories\\TeamRepository;\nuse Jiminny\\Contracts\\Repositories\\UserRepository;\nuse Jiminny\\Exceptions\\InvalidArgumentException;\nuse Jiminny\\Exceptions\\ModelNotFoundException;\nuse Illuminate\\Support\\Collection;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPrompt;\nuse Jiminny\\Models\\AskAnything\\AskAnythingPromptTarget;\nuse Jiminny\\Models\\Activity\\Search;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Group;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AskAnythingRepository;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Repositories\\GroupRepository;\nuse Jiminny\\Repositories\\SearchRepository;\nuse Jiminny\\Repositories\\StageRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ActivityTypeService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\DealStagesService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\RecipientsService;\nuse Mockery;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Tests\\TestCase;\n\nclass AutomatedReportsServiceTest extends TestCase\n{\n private AutomatedReportsService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create a real instance of the service without calling the constructor\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $this->service = $reflection->newInstanceWithoutConstructor();\n\n // Manually set the dependencies using reflection\n $dependencies = [\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ];\n\n foreach ($dependencies as $propertyName => $class) {\n $property = $reflection->getProperty($propertyName);\n $property->setAccessible(true);\n $property->setValue($this->service, $this->createMock($class));\n }\n }\n\n protected function tearDown(): void\n {\n parent::tearDown();\n Mockery::close();\n }\n\n private function getService(\n $mockUserRepository = null,\n $mockStageRepository = null,\n $mockTeamRepository = null,\n ): AutomatedReportsService {\n return new AutomatedReportsService(\n ($mockTeamRepository ?? $this->createMock(TeamRepository::class)),\n $this->createMock(GroupRepository::class),\n ($mockUserRepository ?? $this->createMock(UserRepository::class)),\n ($mockStageRepository ?? $this->createMock(StageRepository::class)),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n }\n\n #[DataProvider('transformMediaTypesDataProvider')]\n public function testTransformMediaTypes(array $mediaTypes, array $expected): void\n {\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformMediaTypes');\n\n $result = $method->invoke($this->service, $report);\n\n $this->assertEquals($expected, $result);\n }\n\n public function testGetMediaTypeFieldDataWithoutReport(): void\n {\n $result = $this->service->getMediaTypeFieldData(null);\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEmpty($result['value']);\n $this->assertEquals('media_types', $result['id']);\n }\n\n public function testGetMediaTypeFieldDataWithReport(): void\n {\n $mediaTypes = ['pdf', 'podcast'];\n $report = new AutomatedReport(['media_types' => $mediaTypes]);\n\n $result = $this->service->getMediaTypeFieldData($report);\n\n $expectedValue = [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ];\n\n $this->assertIsArray($result);\n $this->assertArrayHasKey('value', $result);\n $this->assertEquals($expectedValue, $result['value']);\n }\n\n public static function transformMediaTypesDataProvider(): array\n {\n return [\n 'empty array' => [\n 'mediaTypes' => [],\n 'expected' => [],\n ],\n 'pdf only' => [\n 'mediaTypes' => ['pdf'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ],\n ],\n 'podcast only' => [\n 'mediaTypes' => ['podcast'],\n 'expected' => [\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'both pdf and podcast' => [\n 'mediaTypes' => ['pdf', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n 'with invalid type' => [\n 'mediaTypes' => ['pdf', 'invalid', 'podcast'],\n 'expected' => [\n ['id' => 'pdf', 'name' => 'PDF'],\n ['id' => 'podcast', 'name' => 'Podcast'],\n ],\n ],\n ];\n }\n\n #[DataProvider('hasCallTypeConferenceDataProvider')]\n public function testHasCallTypeConference(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeConference($report);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('hasCallTypeDialerDataProvider')]\n public function testHasCallTypeDialer(array $callTypes, bool $expected): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getCallTypes')->willReturn($callTypes);\n\n $result = $this->service->hasCallTypeDialer($report);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function hasCallTypeConferenceDataProvider(): array\n {\n return [\n 'has conference' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have conference' => [\n 'callTypes' => ['dialer', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public static function hasCallTypeDialerDataProvider(): array\n {\n return [\n 'has dialer' => [\n 'callTypes' => ['conference', 'dialer'],\n 'expected' => true,\n ],\n 'does not have dialer' => [\n 'callTypes' => ['conference', 'other'],\n 'expected' => false,\n ],\n 'empty call types' => [\n 'callTypes' => [],\n 'expected' => false,\n ],\n ];\n }\n\n public function testTransformReportResultsWithEmptyCollection(): void\n {\n $emptyCollection = new Collection([]);\n\n $result = $this->service->transformReportResults($emptyCollection);\n\n $this->assertIsArray($result);\n $this->assertEmpty($result);\n }\n\n public function testTransformReportResultsStructure(): void\n {\n // Create a mock AutomatedReportResult with minimal setup to test structure\n $mockReportResult = $this->createMockReportResult();\n $collection = new Collection([$mockReportResult]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(1, $result);\n\n $transformedResult = $result[0];\n\n // Verify all expected keys are present\n $expectedKeys = [\n 'id', 'name', 'frequency', 'recipients',\n 'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',\n ];\n\n foreach ($expectedKeys as $key) {\n $this->assertArrayHasKey($key, $transformedResult);\n }\n\n // Verify structure of nested arrays\n $this->assertIsArray($transformedResult['frequency']);\n $this->assertArrayHasKey('id', $transformedResult['frequency']);\n $this->assertArrayHasKey('name', $transformedResult['frequency']);\n\n $this->assertIsArray($transformedResult['report_type']);\n $this->assertArrayHasKey('id', $transformedResult['report_type']);\n $this->assertArrayHasKey('name', $transformedResult['report_type']);\n\n $this->assertIsArray($transformedResult['recipients']);\n\n // Verify TODO fields are null as expected\n $this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);\n $this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);\n $this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);\n }\n\n public function testTransformReportResultsWithMultipleResults(): void\n {\n $mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');\n $mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');\n $collection = new Collection([$mockReportResult1, $mockReportResult2]);\n\n $result = $this->service->transformReportResults($collection);\n\n $this->assertIsArray($result);\n $this->assertCount(2, $result);\n\n // Verify different UUIDs\n $this->assertEquals('result-uuid-1', $result[0]['id']);\n $this->assertEquals('result-uuid-2', $result[1]['id']);\n\n // Verify both results have the expected structure\n foreach ($result as $transformedResult) {\n $this->assertArrayHasKey('id', $transformedResult);\n $this->assertArrayHasKey('name', $transformedResult);\n $this->assertArrayHasKey('frequency', $transformedResult);\n $this->assertArrayHasKey('recipients', $transformedResult);\n $this->assertArrayHasKey('report_type', $transformedResult);\n }\n }\n\n #[DataProvider('isUserRecipientOfReportDataProvider')]\n public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn(null);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertEquals($expected, $result);\n }\n\n #[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]\n public function testIsUserRecipientOfAskJiminnyReportViaGroup(\n int $userId,\n ?int $groupId,\n array $recipients,\n array $reportGroups,\n bool $expected,\n ): void {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn($userId);\n $mockUser->method('getGroupId')->willReturn($groupId);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n $mockReport->method('isAskJiminnyReport')->willReturn(true);\n $mockReport->method('getGroups')->willReturn($reportGroups);\n\n $this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void\n {\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n $mockUser->method('getGroupId')->willReturn(5);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n $mockReport->method('getGroups')->willReturn([5]);\n\n $this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));\n }\n\n public static function isUserRecipientOfAskJiminnyReportDataProvider(): array\n {\n return [\n 'group member - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 7,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => true,\n ],\n 'group mismatch - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => 9,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7, 8],\n 'expected' => false,\n ],\n 'user with no group - ask jiminny' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => []],\n 'reportGroups' => [7],\n 'expected' => false,\n ],\n 'recipient users take precedence over group' => [\n 'userId' => 123,\n 'groupId' => null,\n 'recipients' => ['users' => [123]],\n 'reportGroups' => [],\n 'expected' => true,\n ],\n ];\n }\n\n public function testIsUserRecipientOfReportWithEmptyRecipients(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with no recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public function testIsUserRecipientOfReportWithNoUsersKey(): void\n {\n // Create mock User\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getId')->willReturn(123);\n\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);\n\n $result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);\n\n $this->assertFalse($result);\n }\n\n public static function isUserRecipientOfReportDataProvider(): array\n {\n return [\n 'user is recipient - single user' => [\n 'userId' => 123,\n 'recipients' => ['users' => [123]],\n 'expected' => true,\n ],\n 'user is recipient - multiple users' => [\n 'userId' => 456,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => true,\n ],\n 'user is not recipient - single user' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123]],\n 'expected' => false,\n ],\n 'user is not recipient - multiple users' => [\n 'userId' => 999,\n 'recipients' => ['users' => [123, 456, 789]],\n 'expected' => false,\n ],\n 'user is recipient - string IDs converted to int' => [\n 'userId' => 123,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => true,\n ],\n 'user is not recipient - string IDs converted to int' => [\n 'userId' => 999,\n 'recipients' => ['users' => ['123', '456']],\n 'expected' => false,\n ],\n 'empty users array' => [\n 'userId' => 123,\n 'recipients' => ['users' => []],\n 'expected' => false,\n ],\n ];\n }\n\n private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult\n {\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $mockReport->method('getGroups')->willReturn([10, 20]);\n $mockReport->method('getType')->willReturn($reportType);\n\n // Create mock Team\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock Group\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-10');\n $mockGroup->method('getName')->willReturn('Test Team');\n\n $mockQueryBuilder = Mockery::mock();\n $mockQueryBuilder->shouldReceive('where')->andReturnSelf();\n $mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);\n\n $dataRelation = Mockery::mock(HasMany::class);\n $dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);\n $dataRelation->shouldReceive('get')->andReturn(\n new \\Illuminate\\Database\\Eloquent\\Collection([$mockGroup])\n );\n\n $mockTeam->method('groups')->willReturn($dataRelation);\n $mockReport->method('getTeam')->willReturn($mockTeam);\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n $mockReportResult->method('getUuid')->willReturn($uuid);\n $mockReportResult->method('getGeneratedAt')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15T10:30:00Z')\n );\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n\n // Mock methods used in getReportFileName\n $mockReportResult->method('getReportType')->willReturn($reportType);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-08')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2024-01-15')\n );\n $mockReportResult->method('getGroups')->willReturn([10]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n return $mockReportResult;\n }\n\n #[DataProvider('getUsersUuidsDataProvider')]\n public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void\n {\n // Create mock UserRepository\n $mockUserRepository = $this->createMock(UserRepository::class);\n\n // Configure the mock to return specific users for specific IDs using a callback\n $mockUserRepository->method('find')\n ->willReturnCallback(function ($userId) use ($mockUsers) {\n if (! isset($mockUsers[$userId])) {\n return null;\n }\n\n $userUuid = $mockUsers[$userId]['uuid'] ?? null;\n\n if ($userUuid === null) {\n return null;\n }\n\n $mockUser = $this->createMock(\\Jiminny\\Models\\User::class);\n $mockUser->method('getUuid')->willReturn((string) $userUuid);\n\n return $mockUser;\n });\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn($recipients);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetUsersUuidsWithEmptyRecipients(): void\n {\n // Create mock AutomatedReport with empty recipients\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn([]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNoUsersKey(): void\n {\n // Create mock AutomatedReport with recipients but no 'users' key\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);\n\n $result = $this->service->getUsersUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetUsersUuidsWithNonExistentUsers(): void\n {\n // Create mock UserRepository that returns null for all users\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn(null);\n\n // Create service with mocked UserRepository\n $automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);\n\n $result = $automatedReportsService->getUsersUuids($mockReport);\n\n // Should return array with null values for non-existent users\n $this->assertEquals([], $result);\n }\n\n public static function getUsersUuidsDataProvider(): array\n {\n return [\n 'single user found' => [\n 'recipients' => ['users' => [123]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n ],\n 'expectedUuids' => ['user-uuid-123'],\n ],\n 'multiple users found' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n 456 => ['id' => 456, 'uuid' => 'user-uuid-456'],\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],\n ],\n 'mixed found and not found users' => [\n 'recipients' => ['users' => [123, 456, 789]],\n 'mockUsers' => [\n 123 => ['id' => 123, 'uuid' => 'user-uuid-123'],\n // 456 not found in DB\n 789 => ['id' => 789, 'uuid' => 'user-uuid-789'],\n ],\n 'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out\n ],\n 'empty users array' => [\n 'recipients' => ['users' => []],\n 'mockUsers' => [],\n 'expectedUuids' => [],\n ],\n 'all users not found' => [\n 'recipients' => ['users' => [123, 456]],\n 'mockUsers' => [], // No users found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getCurrentDealStagesUuidsDataProvider')]\n public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void\n {\n // Create mock StageRepository\n $mockStageRepository = $this->createMock(StageRepository::class);\n\n // Configure the mock to return specific stages for specific IDs using a callback\n $mockStageRepository->method('find')\n ->willReturnCallback(function ($stageId) use ($mockStages) {\n if (! isset($mockStages[$stageId])) {\n return null;\n }\n\n $stageUuid = $mockStages[$stageId]['uuid'] ?? null;\n\n if ($stageUuid === null) {\n return null;\n }\n\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn((string) $stageUuid);\n\n return $mockStage;\n });\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals($expectedUuids, $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithEmptyStages(): void\n {\n // Create mock AutomatedReport with empty current deal stages\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n\n $result = $this->service->getCurrentDealStagesUuids($mockReport);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void\n {\n // Create mock StageRepository that returns null for all stages\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturn(null);\n\n // Create service with mocked StageRepository\n $automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);\n\n $result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);\n\n // Should return array with null values for non-existent stages\n $this->assertEquals([], $result);\n }\n\n public static function getCurrentDealStagesUuidsDataProvider(): array\n {\n return [\n 'single stage found' => [\n 'currentDealStages' => [10],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n ],\n 'expectedUuids' => ['stage-uuid-10'],\n ],\n 'multiple stages found' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n 20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],\n ],\n 'mixed found and not found stages' => [\n 'currentDealStages' => [10, 20, 30],\n 'mockStages' => [\n 10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],\n // 20 not found in DB\n 30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],\n ],\n 'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out\n ],\n 'empty stages array' => [\n 'currentDealStages' => [],\n 'mockStages' => [],\n 'expectedUuids' => [],\n ],\n 'all stages not found' => [\n 'currentDealStages' => [10, 20],\n 'mockStages' => [], // No stages found\n 'expectedUuids' => [], // Updated to reflect that nulls are filtered out\n ],\n ];\n }\n\n #[DataProvider('getTeamGroupsDataProvider')]\n public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n if ($mockTeamData === null) {\n // Team not found\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn(null);\n } else {\n // Team found - create mock team with groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create mock groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n\n // Create mock Group objects\n $groupObjects = [];\n foreach ($mockGroups as $groupData) {\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getUuid')->willReturn($groupData['id']);\n $mockGroup->method('getName')->willReturn($groupData['name']);\n $groupObjects[] = $mockGroup;\n }\n\n // Mock the groups collection to return our mock groups\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator($groupObjects));\n\n // Mock the groups() relation\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')\n ->with($teamUuid)\n ->willReturn($mockTeam);\n }\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups($teamUuid);\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamGroupsWithNonExistentTeam(): void\n {\n // Create mock TeamRepository that returns null (team not found)\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn(null);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamGroupsWithEmptyGroups(): void\n {\n // Create mock team with no groups\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n\n // Create empty groups collection\n $mockGroupsCollection = $this->createMock(\\Illuminate\\Database\\Eloquent\\Collection::class);\n $mockGroupsCollection->method('getIterator')->willReturn(new \\ArrayIterator([]));\n\n $mockGroupsRelation = $this->createMock(\\Illuminate\\Database\\Eloquent\\Relations\\HasMany::class);\n $mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeamGroups('team-with-no-groups');\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamGroupsDataProvider(): array\n {\n return [\n 'team with single group' => [\n 'teamUuid' => 'team-uuid-123',\n 'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'team with multiple groups' => [\n 'teamUuid' => 'team-uuid-456',\n 'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],\n 'mockGroups' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n 'expectedResult' => [\n ['id' => 'group-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'group-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'group-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'team not found' => [\n 'teamUuid' => 'non-existent-uuid',\n 'mockTeamData' => null,\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n 'team with no groups' => [\n 'teamUuid' => 'team-uuid-empty',\n 'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],\n 'mockGroups' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('getTeamsDataProvider')]\n public function testGetTeams(array $mockTeams, array $expectedResult): void\n {\n // Create mock TeamRepository\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n // Create mock Team objects\n $teamObjects = [];\n foreach ($mockTeams as $teamData) {\n $mockTeam = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam->method('getUuid')->willReturn($teamData['id']);\n $mockTeam->method('getName')->willReturn($teamData['name']);\n $mockTeam->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn($teamData['hasAutomatedReports']);\n $teamObjects[] = $mockTeam;\n }\n\n // Mock the repository to return a Collection (not array)\n $mockTeamRepository->method('getTeamsForKiosk')\n ->with('active')\n ->willReturn(new Collection($teamObjects));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals($expectedResult, $result);\n }\n\n public function testGetTeamsWithNoTeams(): void\n {\n // Create mock TeamRepository that returns empty Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public function testGetTeamsWithAllTeamsWithoutFeature(): void\n {\n // Create mock teams without AUTOMATED_REPORTS feature\n $mockTeam1 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam1->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n $mockTeam2 = $this->createMock(\\Jiminny\\Models\\Team::class);\n $mockTeam2->method('hasFeature')\n ->with(\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(false);\n\n // Create mock TeamRepository that returns Collection\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n // Create service with mocked TeamRepository\n $automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $automatedReportsService->getTeams();\n\n $this->assertEquals([], $result);\n }\n\n public static function getTeamsDataProvider(): array\n {\n return [\n 'single team with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ],\n ],\n 'multiple teams with feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-2', 'name' => 'Marketing Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'mixed teams - some with feature, some without' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => true,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-3',\n 'name' => 'Support Team',\n 'hasAutomatedReports' => true,\n ],\n ],\n 'expectedResult' => [\n ['id' => 'team-uuid-1', 'name' => 'Sales Team'],\n ['id' => 'team-uuid-3', 'name' => 'Support Team'],\n ],\n ],\n 'all teams without feature' => [\n 'mockTeams' => [\n [\n 'id' => 'team-uuid-1',\n 'name' => 'Sales Team',\n 'hasAutomatedReports' => false,\n ],\n [\n 'id' => 'team-uuid-2',\n 'name' => 'Marketing Team',\n 'hasAutomatedReports' => false,\n ],\n ],\n 'expectedResult' => [],\n ],\n 'empty teams array' => [\n 'mockTeams' => [],\n 'expectedResult' => [],\n ],\n ];\n }\n\n #[DataProvider('deleteS3FilesDataProvider')]\n public function testDeleteS3Files(\n string $mediaType,\n array $expectedFileExtensions,\n array $existingFiles,\n string $pathSuffix,\n int $expectedDeletes\n ): void {\n // Arrange\n $teamUuid = 'team-uuid-123';\n $reportUuid = 'report-uuid-456';\n $basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);\n\n $team = Mockery::mock(Team::class);\n $team->allows('getUuid')->andReturn($teamUuid);\n\n $report = Mockery::mock(AutomatedReport::class);\n $report->allows('getTeam')->andReturn($team);\n\n $result = Mockery::mock(AutomatedReportResult::class);\n $result->allows('getReport')->andReturn($report);\n $result->allows('getUuid')->andReturn($reportUuid);\n $result->allows('getMediaType')->andReturn($mediaType);\n\n Storage::fake();\n Log::shouldReceive('info')->times($expectedDeletes);\n\n foreach ($existingFiles as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n Storage::put($filePath, 'dummy content');\n }\n\n // Act\n $this->service->deleteS3Files($result);\n\n // Assert\n foreach ($expectedFileExtensions as $extension) {\n $filePath = $basePath . $pathSuffix . '.' . $extension;\n if (in_array($extension, $existingFiles, true)) {\n Storage::assertMissing($filePath);\n } else {\n // To be sure no unexpected files were created and deleted\n Storage::assertMissing($filePath);\n }\n }\n }\n\n public static function deleteS3FilesDataProvider(): array\n {\n return [\n 'PDF report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'MD', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 3,\n ],\n 'PDF report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => ['html', 'pdf'],\n 'pathSuffix' => '',\n 'expectedDeletes' => 2,\n ],\n 'PDF report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,\n 'expectedFileExtensions' => ['html', 'MD', 'pdf'],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n 'Podcast report, all files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['json', 'mp3', 'ssml'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 3,\n ],\n 'Podcast report, some files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => ['mp3'],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 1,\n ],\n 'Podcast report, no files exist' => [\n 'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,\n 'expectedFileExtensions' => ['json', 'mp3', 'ssml'],\n 'existingFiles' => [],\n 'pathSuffix' => '_podcast',\n 'expectedDeletes' => 0,\n ],\n 'Other media type, should do nothing' => [\n 'mediaType' => 'some_other_type',\n 'expectedFileExtensions' => [],\n 'existingFiles' => [],\n 'pathSuffix' => '',\n 'expectedDeletes' => 0,\n ],\n ];\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void\n {\n // Create mocks for the test\n $automatedReportsService = Mockery::mock(AutomatedReportsService::class);\n\n $team = Mockery::mock(Team::class);\n $team->shouldReceive('getId')->andReturn(123);\n\n $from = now()->subDays(30);\n $to = now();\n $source = 'test-source';\n\n // Expect the method to be called with specific parameters\n $automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')\n ->once()\n ->with(\n $team,\n Mockery::on(function ($arg) use ($from) {\n return $arg->timestamp === $from->timestamp;\n }),\n Mockery::on(function ($arg) use ($to) {\n return $arg->timestamp === $to->timestamp;\n }),\n $source\n )\n ->andReturn(5);\n\n // Call the method and verify the result\n $result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(\n $team,\n $from,\n $to,\n $source\n );\n\n $this->assertEquals(5, $result);\n }\n\n #[DataProvider('sanitizeFileNameDataProvider')]\n public function testSanitizeFileName(string $input, string $expected): void\n {\n $result = $this->service->sanitizeFileName($input);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function sanitizeFileNameDataProvider(): array\n {\n return [\n 'no special characters' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team',\n ],\n 'forward slash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND/IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'backslash in team name' => [\n 'input' => 'Exec Summary - Sep 2025 - ND\\IRV',\n 'expected' => 'Exec Summary - Sep 2025 - ND-IRV',\n ],\n 'multiple forward slashes' => [\n 'input' => 'Report - Team A/B/C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'multiple backslashes' => [\n 'input' => 'Report - Team A\\B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'mixed slashes and backslashes' => [\n 'input' => 'Report - Team A/B\\C',\n 'expected' => 'Report - Team A-B-C',\n ],\n 'complex team name with slashes' => [\n 'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',\n 'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',\n ],\n 'only slashes' => [\n 'input' => '//\\\\\\\\',\n 'expected' => '----',\n ],\n 'empty string' => [\n 'input' => '',\n 'expected' => '',\n ],\n 'slash at start' => [\n 'input' => '/Report Name',\n 'expected' => '-Report Name',\n ],\n 'slash at end' => [\n 'input' => 'Report Name/',\n 'expected' => 'Report Name-',\n ],\n ];\n }\n\n public function testGetReportFileNameSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with slash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileName\n $result = $service->getReportFileName($mockReportResult);\n\n // Verify the result does not contain slashes or backslashes\n $this->assertStringNotContainsString('/', $result);\n $this->assertStringNotContainsString('\\\\', $result);\n\n // Verify the slash was replaced with dash\n $this->assertStringContainsString('ND-IRV', $result);\n }\n\n public function testGetReportFileNameWithExtensionSanitizesOutput(): void\n {\n // Create mock GroupRepository\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n\n // Create mock Group with backslash in name\n $mockGroup = $this->createMock(\\Jiminny\\Models\\Group::class);\n $mockGroup->method('getName')->willReturn('Team\\Name');\n\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n // Create service with mocked GroupRepository\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n // Create mock AutomatedReportResult\n $mockReportResult = $this->createMock(AutomatedReportResult::class);\n\n // Create mock AutomatedReport\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getFrequency')->willReturn('monthly');\n\n $mockReportResult->method('getReport')->willReturn($mockReport);\n $mockReportResult->method('getFromDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-01')\n );\n $mockReportResult->method('getToDate')->willReturn(\n \\Illuminate\\Support\\Carbon::parse('2025-09-30')\n );\n $mockReportResult->method('getGroups')->willReturn([123]);\n $mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n // Call getReportFileNameWithExtension\n $result = $service->getReportFileNameWithExtension($mockReportResult);\n\n // Verify the result does not contain backslashes\n $this->assertStringNotContainsString('\\\\', $result);\n $this->assertStringNotContainsString('/', $result);\n\n // Verify the backslash was replaced with dash\n $this->assertStringContainsString('Team-Name', $result);\n\n // Verify extension is added\n $this->assertStringEndsWith('.pdf', $result);\n }\n\n public function testHasPassedScheduledTimeWithNullGeneratedAt(): void\n {\n $result = $this->service->hasPassedScheduledTime(null, 'America/Chicago');\n\n $this->assertFalse($result);\n }\n\n public function testHasPassedScheduledTimeWhenScheduledTimePassed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testHasPassedScheduledTimeBeforeScheduledTimeToday(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 04:00:00', 'America/Chicago'));\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->hasPassedScheduledTime($generatedAt, 'America/Chicago');\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWithEmptyUsers(): void\n {\n $result = $this->service->shouldSendReport([]);\n\n $this->assertFalse($result);\n }\n\n public function testShouldSendReportAtScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 05:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportNotAtScheduledTimeWithoutGeneratedAt(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $result = $this->service->shouldSendReport($users);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenScheduledTimeMissed(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 01:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertTrue($result);\n\n Carbon::setTestNow();\n }\n\n public function testShouldSendReportWhenGeneratedAfterScheduledTime(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-02-24 10:00:00', 'America/Chicago'));\n\n $users = [\n ['email' => 'test@example.com', 'name' => 'Test User', 'timezone' => 'America/Chicago'],\n ];\n\n $generatedAt = Carbon::parse('2026-02-24 06:00:00', 'America/Chicago');\n\n $result = $this->service->shouldSendReport($users, $generatedAt);\n\n $this->assertFalse($result);\n\n Carbon::setTestNow();\n }\n\n public function testGetTypes(): void\n {\n $types = AutomatedReportsService::getTypes();\n\n $this->assertIsArray($types);\n $this->assertContains('exec_summary', $types);\n $this->assertContains('coaching_profiles', $types);\n $this->assertContains('loss_analysis', $types);\n $this->assertNotContains('ask_jiminny', $types);\n }\n\n public function testGetCallTypes(): void\n {\n $callTypes = AutomatedReportsService::getCallTypes();\n\n $this->assertIsArray($callTypes);\n $this->assertContains('conference', $callTypes);\n $this->assertContains('dialer', $callTypes);\n }\n\n public function testGetFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertContains('quarterly', $frequencies);\n $this->assertContains('one_off', $frequencies);\n $this->assertNotContains('daily', $frequencies);\n }\n\n public function testGetAskJiminnyFrequencies(): void\n {\n $frequencies = AutomatedReportsService::getAskJiminnyFrequencies();\n\n $this->assertIsArray($frequencies);\n $this->assertContains('daily', $frequencies);\n $this->assertContains('weekly', $frequencies);\n $this->assertContains('monthly', $frequencies);\n $this->assertNotContains('quarterly', $frequencies);\n $this->assertNotContains('one_off', $frequencies);\n }\n\n public function testGetReportEnabledFieldData(): void\n {\n $result = $this->service->getReportEnabledFieldData(true);\n\n $this->assertEquals('report_enabled', $result['id']);\n $this->assertTrue($result['value']);\n }\n\n public function testGetReportEnabledFieldDataDefault(): void\n {\n $result = $this->service->getReportEnabledFieldData();\n\n $this->assertFalse($result['value']);\n }\n\n public function testGetOrganizationFieldDataShortVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData(null, true);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetOrganizationFieldDataFullVersion(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getOrganizationFieldData('team-uuid-1', false);\n\n $this->assertEquals('organization', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('team-uuid-1', $result['value']);\n $this->assertArrayHasKey('dependencies', $result);\n }\n\n public function testGetTeamFieldDataShortVersion(): void\n {\n $result = $this->service->getTeamFieldData([], [], true);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetTeamFieldDataFullVersion(): void\n {\n $result = $this->service->getTeamFieldData(['opt1'], ['val1'], false);\n\n $this->assertEquals('teams', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals(['opt1'], $result['options']);\n $this->assertEquals(['val1'], $result['value']);\n }\n\n public function testGetReportTypeFieldDataShortVersion(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayNotHasKey('inputType', $result);\n }\n\n public function testGetReportTypeFieldDataFullVersion(): void\n {\n $result = $this->service->getReportTypeFieldData('exec_summary', false);\n\n $this->assertEquals('report_type', $result['id']);\n $this->assertArrayHasKey('inputType', $result);\n $this->assertEquals('exec_summary', $result['value']);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingBothFeatures(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertLessThan(\n array_search(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids),\n array_search('exec_summary', $ids)\n );\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAutomatedReports(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, true],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, false],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetReportTypeFieldDataWithTeamHavingOnlyAskJiminny(): void\n {\n $team = $this->createMock(\\Jiminny\\Models\\Team::class);\n $team->method('hasFeature')->willReturnMap([\n [\\Jiminny\\Models\\Feature\\FeatureEnum::AUTOMATED_REPORTS, false],\n [\\Jiminny\\Models\\Feature\\FeatureEnum::ASK_JIMINNY_REPORTS, true],\n ]);\n\n $result = $this->service->getReportTypeFieldData(null, true, $team);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n $this->assertNotContains('exec_summary', $ids);\n }\n\n public function testGetReportTypeFieldDataWithNullTeamFallsBackToStandardTypes(): void\n {\n $result = $this->service->getReportTypeFieldData(null, true, null);\n\n $ids = array_column($result['options'], 'id');\n $this->assertContains('exec_summary', $ids);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $ids);\n }\n\n public function testGetFrequencyFieldData(): void\n {\n $result = $this->service->getFrequencyFieldData('weekly');\n\n $this->assertEquals('frequency', $result['id']);\n $this->assertEquals('weekly', $result['value']);\n $this->assertArrayHasKey('options', $result);\n }\n\n public function testGetPeriodFieldData(): void\n {\n $result = $this->service->getPeriodFieldData('2025-01-01', '2025-01-31');\n\n $this->assertEquals('period', $result['id']);\n $this->assertEquals('2025-01-01', $result['value']['startDate']);\n $this->assertEquals('2025-01-31', $result['value']['endDate']);\n }\n\n public function testGetCallDurationFieldData(): void\n {\n $result = $this->service->getCallDurationFieldData(5, 60);\n\n $this->assertEquals('call_duration', $result['id']);\n $this->assertEquals(5, $result['value']['min']);\n $this->assertEquals(60, $result['value']['max']);\n }\n\n public function testGetDealValueFieldData(): void\n {\n $result = $this->service->getDealValueFieldData(1000, 5000);\n\n $this->assertEquals('deal_value', $result['id']);\n $this->assertEquals(1000, $result['value']['min']);\n $this->assertEquals(5000, $result['value']['max']);\n }\n\n public function testGetCustomReportNameFieldData(): void\n {\n $result = $this->service->getCustomReportNameFieldData('My Report');\n\n $this->assertEquals('custom_name', $result['id']);\n $this->assertEquals('My Report', $result['value']);\n }\n\n public function testGetAdditionalPromptInputFieldData(): void\n {\n $result = $this->service->getAdditionalPromptInputFieldData('Some prompt');\n\n $this->assertEquals('additional_prompt_input', $result['id']);\n $this->assertEquals('Some prompt', $result['value']);\n }\n\n public function testGetCallTypeFieldDataBothOn(): void\n {\n $result = $this->service->getCallTypeFieldData(true, true);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertCount(2, $result['value']);\n }\n\n public function testGetCallTypeFieldDataNoneOn(): void\n {\n $result = $this->service->getCallTypeFieldData(false, false);\n\n $this->assertEquals('call_type', $result['id']);\n $this->assertEmpty($result['value']);\n }\n\n public function testGetCallTypeFieldDataConferenceOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(true, false);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('conference', $result['value'][0]['id']);\n }\n\n public function testGetCallTypeFieldDataDialerOnly(): void\n {\n $result = $this->service->getCallTypeFieldData(false, true);\n\n $this->assertCount(1, $result['value']);\n $this->assertEquals('dialer', $result['value'][0]['id']);\n }\n\n public function testTransformDurationToMinutesNull(): void\n {\n $result = $this->service->transformDurationToMinutes(null);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutesZero(): void\n {\n $result = $this->service->transformDurationToMinutes(0);\n\n $this->assertNull($result);\n }\n\n public function testTransformDurationToMinutes(): void\n {\n $result = $this->service->transformDurationToMinutes(300);\n\n $this->assertEquals(5, $result);\n }\n\n public function testGetTeam(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('idOrUuid')\n ->with('team-uuid')\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeam('team-uuid');\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetTeamById(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n $mockTeamRepository->expects($this->once())\n ->method('find')\n ->with(42)\n ->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n $result = $service->getTeamById(42);\n\n $this->assertSame($mockTeam, $result);\n }\n\n public function testGetGroupsUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([]);\n\n $result = $this->service->getGroupsUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetGroupsUuidsWithGroups(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getUuid')->willReturn('group-uuid-1');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getGroups')->willReturn([10, 99]);\n\n $result = $service->getGroupsUuids($report);\n\n $this->assertEquals(['group-uuid-1'], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([]);\n\n $result = $this->service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetPlaybookCategoriesUuidsWithCategories(): void\n {\n $mockCategory = $this->createMock(\\Jiminny\\Models\\PlaybookCategory::class);\n $mockCategory->method('getUuid')->willReturn('cat-uuid-1');\n\n $mockPlaybookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);\n $mockPlaybookCategoryRepository->method('find')->willReturnMap([\n [1, $mockCategory],\n [2, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $mockPlaybookCategoryRepository,\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getPlaybookCategories')->willReturn([1, 2]);\n\n $result = $service->getPlaybookCategoriesUuids($report);\n\n $this->assertEquals(['cat-uuid-1'], $result);\n }\n\n public function testGetDealAtCallStagesUuidsEmpty(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([]);\n\n $result = $this->service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals([], $result);\n }\n\n public function testGetDealAtCallStagesUuidsWithStages(): void\n {\n $mockStage = $this->createMock(\\Jiminny\\Models\\Stage::class);\n $mockStage->method('getUuid')->willReturn('stage-uuid-1');\n\n $mockStageRepository = $this->createMock(StageRepository::class);\n $mockStageRepository->method('find')->willReturnMap([\n [5, $mockStage],\n [9, null],\n ]);\n\n $service = $this->getService(mockStageRepository: $mockStageRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getDealAtCallStages')->willReturn([5, 9]);\n\n $result = $service->getDealAtCallStagesUuids($report);\n\n $this->assertEquals(['stage-uuid-1'], $result);\n }\n\n public function testGetJiminnyUsersUuids(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getJiminnyUsersUuids($report);\n\n $this->assertEquals(['user-uuid-1'], $result);\n }\n\n public function testGetRecipientUsers(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getEmailAddress')->willReturn('user@test.com');\n $mockUser->method('getName')->willReturn('Test User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n\n $result = $service->getRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n $this->assertEquals('Test User', $result[0]['name']);\n }\n\n public function testGetValidRecipientUsersFiltersEmptyEmail(): void\n {\n $mockUserWithEmail = $this->createMock(User::class);\n $mockUserWithEmail->method('getEmailAddress')->willReturn('valid@test.com');\n $mockUserWithEmail->method('getName')->willReturn('Valid User');\n $timezone = $this->createMock(\\DateTimeZone::class);\n $timezone->method('getName')->willReturn('UTC');\n $mockUserWithEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserNoEmail = $this->createMock(User::class);\n $mockUserNoEmail->method('getEmailAddress')->willReturn('');\n $mockUserNoEmail->method('getName')->willReturn('No Email User');\n $mockUserNoEmail->method('getTimezone')->willReturn($timezone);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUserWithEmail],\n [2, $mockUserNoEmail],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1, 2]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('valid@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersWithJiminny(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $mockUser1 = $this->createMock(User::class);\n $mockUser1->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser1->method('getName')->willReturn('User1');\n $mockUser1->method('getTimezone')->willReturn($tz);\n\n $mockUser2 = $this->createMock(User::class);\n $mockUser2->method('getEmailAddress')->willReturn('jiminny@test.com');\n $mockUser2->method('getName')->willReturn('Jiminny');\n $mockUser2->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, $mockUser1],\n [2, $mockUser2],\n ]);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => [2]]);\n\n $result = $service->getValidRecipientUsers($report, true);\n\n $this->assertCount(2, $result);\n }\n\n public function testGetReportTypeName(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getType')->willReturn('exec_summary');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n\n $result = $this->service->getReportTypeName($mockResult);\n\n $this->assertEquals('Exec Summary', $result);\n }\n\n public function testGetReportPeriodNameThrowsOnNullFrom(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2025-01-15'));\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n public function testGetReportPeriodNameThrowsOnNullTo(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2025-01-08'));\n $mockResult->method('getToDate')->willReturn(null);\n\n $this->expectException(\\Jiminny\\Exceptions\\ApplicationException::class);\n\n $this->service->getReportPeriodName($mockResult);\n }\n\n #[DataProvider('formatReportPeriodNameDataProvider')]\n public function testGetReportPeriodName(\n string $frequency,\n string $from,\n string $to,\n string $expected\n ): void {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn($frequency);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse($from));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse($to));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals($expected, $result);\n }\n\n public static function formatReportPeriodNameDataProvider(): array\n {\n return [\n 'daily' => [\n 'frequency' => 'daily',\n 'from' => '2025-05-15',\n 'to' => '2025-05-15',\n 'expected' => '15 May 2025',\n ],\n 'monthly same year' => [\n 'frequency' => 'monthly',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => 'May 2025',\n ],\n 'weekly same month' => [\n 'frequency' => 'weekly',\n 'from' => '2025-08-04',\n 'to' => '2025-08-08',\n 'expected' => '4 - 8 Aug 2025',\n ],\n 'weekly different months same year' => [\n 'frequency' => 'weekly',\n 'from' => '2025-10-27',\n 'to' => '2025-11-03',\n 'expected' => '27 Oct - 3 Nov 2025',\n ],\n 'weekly different years' => [\n 'frequency' => 'weekly',\n 'from' => '2024-12-28',\n 'to' => '2025-01-03',\n 'expected' => '28 Dec 2024 - 3 Jan 2025',\n ],\n 'quarterly same year' => [\n 'frequency' => 'quarterly',\n 'from' => '2025-01-01',\n 'to' => '2025-04-01',\n 'expected' => 'Jan - Mar 2025',\n ],\n 'quarterly different years' => [\n 'frequency' => 'quarterly',\n 'from' => '2024-11-01',\n 'to' => '2025-02-01',\n 'expected' => 'Nov 2024 - Jan 2025',\n ],\n 'one_off same month' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-02',\n 'to' => '2025-05-31',\n 'expected' => '2 - 31 May 2025',\n ],\n 'one_off different months same year' => [\n 'frequency' => 'one_off',\n 'from' => '2025-05-15',\n 'to' => '2025-06-15',\n 'expected' => '15 May - 15 Jun 2025',\n ],\n 'one_off different years' => [\n 'frequency' => 'one_off',\n 'from' => '2024-12-15',\n 'to' => '2025-01-15',\n 'expected' => '15 Dec 2024 - 15 Jan 2025',\n ],\n 'unknown frequency falls back to default' => [\n 'frequency' => 'unknown',\n 'from' => '2025-05-01',\n 'to' => '2025-05-31',\n 'expected' => '1 May 2025 - 31 May 2025',\n ],\n ];\n }\n\n public function testGetReportTeamsNameEmpty(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([]);\n\n $result = $this->service->getReportTeamsName($mockResult);\n\n $this->assertEquals('All', $result);\n }\n\n public function testGetReportTeamsNameSingleGroup(): void\n {\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getName')->willReturn('Sales Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team', $result);\n }\n\n public function testGetReportTeamsNameMultipleGroups(): void\n {\n $mockGroup1 = $this->createMock(Group::class);\n $mockGroup1->method('getName')->willReturn('Sales Team');\n\n $mockGroup2 = $this->createMock(Group::class);\n $mockGroup2->method('getName')->willReturn('Marketing Team');\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturnMap([\n [10, $mockGroup1],\n [20, $mockGroup2],\n [99, null],\n ]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getGroups')->willReturn([10, 20, 99]);\n\n $result = $service->getReportTeamsName($mockResult);\n\n $this->assertEquals('Sales Team, Marketing Team', $result);\n }\n\n public function testGetReportFound(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReport('report-uuid');\n\n $this->assertSame($mockReport, $result);\n }\n\n public function testGetReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReport('non-existent-uuid');\n }\n\n public function testDeleteReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->delete('non-existent-uuid');\n }\n\n public function testDeleteReportSuccess(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->expects($this->once())->method('delete');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $service->delete('report-uuid');\n }\n\n public function testUpdateStatusNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->updateStatus('non-existent-uuid', ['report_enabled' => true]);\n }\n\n public function testGetReportResultFound(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findResultByUuid')\n ->with('result-uuid')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResult('result-uuid');\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testGetReportResultNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findResultByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->getReportResult('non-existent-uuid');\n }\n\n public function testFindChildResult(): void\n {\n $mockParent = $this->createMock(AutomatedReportResult::class);\n $mockChild = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findChildResult')\n ->with($mockParent, 'podcast')\n ->willReturn($mockChild);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->findChildResult($mockParent, 'podcast');\n\n $this->assertSame($mockChild, $result);\n }\n\n #[DataProvider('calculateFromAndToDatePeriodDataProvider')]\n public function testCalculateFromAndToDatePeriod(string $frequency): void\n {\n Carbon::setTestNow(Carbon::parse('2025-06-15 12:00:00'));\n\n $result = $this->service->calculateFromAndToDatePeriod($frequency);\n\n $this->assertArrayHasKey('fromDate', $result);\n $this->assertArrayHasKey('toDate', $result);\n $this->assertInstanceOf(Carbon::class, $result['fromDate']);\n $this->assertInstanceOf(Carbon::class, $result['toDate']);\n\n Carbon::setTestNow();\n }\n\n public static function calculateFromAndToDatePeriodDataProvider(): array\n {\n return [\n 'daily' => ['daily'],\n 'weekly' => ['weekly'],\n 'monthly' => ['monthly'],\n 'quarterly' => ['quarterly'],\n ];\n }\n\n public function testCalculateFromAndToDatePeriodOneOff(): void\n {\n $from = IlluminateCarbon::parse('2025-01-01');\n $to = IlluminateCarbon::parse('2025-01-31');\n\n $result = $this->service->calculateFromAndToDatePeriod('one_off', $from, $to);\n\n $this->assertSame($from, $result['fromDate']);\n $this->assertSame($to, $result['toDate']);\n }\n\n public function testCalculateFromAndToDatePeriodInvalidFrequency(): void\n {\n $this->expectException(InvalidArgumentException::class);\n\n $this->service->calculateFromAndToDatePeriod('invalid_frequency');\n }\n\n public function testGetMediaPath(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn('https://example.com/reports/file.pdf');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/reports/file.pdf', $result);\n }\n\n public function testGetMediaPathPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n $mockResult->method('getPodcastAudioUrl')->willReturn('https://example.com/audio/file.mp3');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertEquals('/audio/file.mp3', $result);\n }\n\n public function testGetMediaPathNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMediaPathPdfNullUrl(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->method('getPdfUrl')->willReturn(null);\n\n $result = $this->service->getMediaPath($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetFilenameSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertEquals('Podcast', $result);\n }\n\n public function testGetFilenameSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getFilenameSuffix($mockResult);\n\n $this->assertNull($result);\n }\n\n public function testGetMailSubjectSuffixPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('report', $result);\n }\n\n public function testGetMailSubjectSuffixPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('podcast', $result);\n }\n\n public function testGetMailSubjectSuffixUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown_type');\n\n $result = $this->service->getMailSubjectSuffix($mockResult);\n\n $this->assertEquals('', $result);\n }\n\n public function testGetMediaTypeMetadataPdf(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('pdf', $result['extension']);\n $this->assertEquals('application/pdf', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataPodcast(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertEquals('mp3', $result['extension']);\n $this->assertEquals('audio/mpeg', $result['mime']);\n }\n\n public function testGetMediaTypeMetadataUnknown(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getMediaType')->willReturn('unknown');\n\n $result = $this->service->getMediaTypeMetadata($mockResult);\n\n $this->assertNull($result['extension']);\n $this->assertNull($result['mime']);\n }\n\n public function testGetTeamIdsWithReportsResults(): void\n {\n $expected = new Collection([1, 2, 3]);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getTeamIdsWithReportsResults')\n ->with(5)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamIdsWithReportsResults(5);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetTeamReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getTeamReports($mockTeam);\n\n $this->assertSame($expected, $result);\n }\n\n public function testGetReportResults(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $expected = new \\Illuminate\\Database\\Eloquent\\Collection();\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn($expected);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getReportResults($mockReport);\n\n $this->assertSame($expected, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodNoReports(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportIdsByTeam')\n ->willReturn(new Collection([]));\n $mockRepo->expects($this->never())\n ->method('getReportResultsQueryForRetention');\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testDeleteReportsResultsInRetentionPeriodWithNoQueryResults(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n $retentionDate = \\Carbon\\CarbonImmutable::parse('2025-01-01');\n\n $mockQuery = Mockery::mock(Builder::class);\n $mockQuery->shouldReceive('exists')->andReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('getReportIdsByTeam')->willReturn(new Collection([1, 2]));\n $mockRepo->method('getReportResultsQueryForRetention')->willReturn($mockQuery);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $result = $service->deleteReportsResultsInRetentionPeriod($mockTeam, $retentionDate);\n\n $this->assertEquals(0, $result);\n }\n\n public function testUpdateAskJiminnyReportStatusNotAskJiminny(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('isAskJiminnyReport')->willReturn(false);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $mockUser = $this->createMock(User::class);\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report is not an Ask Jiminny report');\n\n $service->updateAskJiminnyReport($mockReport, [], $mockUser);\n }\n\n public function testGetAskJiminnyReportFilters(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearch->method('getUuid')->willReturn('search-uuid-1');\n $mockSearch->method('getName')->willReturn('My Search');\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->expects($this->once())\n ->method('findByUserOrderedByName')\n ->with($mockUser)\n ->willReturn(new Collection([$mockSearch]));\n\n $mockPromptDto = new AskAnythingPromptDto(\n id: 'prompt-uuid-1',\n title: 'My Prompt',\n content: 'Prompt text',\n target: AskAnythingPromptTarget::on_demand,\n );\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->expects($this->once())\n ->method('get')\n ->with($mockUser, AskAnythingPromptTarget::on_demand)\n ->willReturn([$mockPromptDto]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFilters($mockUser);\n\n $this->assertCount(2, $result);\n $promptFilter = collect($result)->firstWhere('id', 'prompt');\n $searchFilter = collect($result)->firstWhere('id', 'saved_search');\n\n $this->assertCount(1, $promptFilter['options']);\n $this->assertEquals('prompt-uuid-1', $promptFilter['options'][0]['id']);\n $this->assertCount(1, $searchFilter['options']);\n $this->assertEquals('search-uuid-1', $searchFilter['options'][0]['id']);\n }\n\n public function testGetAskJiminnyReportFormDataWithoutReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser);\n\n $this->assertArrayHasKey('fields', $result);\n $this->assertIsArray($result['fields']);\n\n $fieldIds = array_column($result['fields'], 'id');\n $this->assertContains('enabled', $fieldIds);\n $this->assertContains('report_name', $fieldIds);\n $this->assertContains('frequency', $fieldIds);\n $this->assertContains('expires_on', $fieldIds);\n $this->assertContains('saved_search', $fieldIds);\n $this->assertContains('ask_jiminny_prompt', $fieldIds);\n }\n\n public function testGetAskJiminnyReportFormDataWithReport(): void\n {\n $timezone = new \\DateTimeZone('UTC');\n\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getTimezone')->willReturn($timezone);\n\n $mockTeam = $this->createMock(Team::class);\n $mockUser->method('getTeam')->willReturn($mockTeam);\n\n $mockSavedSearch = $this->createMock(Search::class);\n $mockSavedSearch->method('getUuid')->willReturn('search-uuid');\n $mockSavedSearch->method('getName')->willReturn('My Search');\n\n $mockPromptModel = $this->createMock(AskAnythingPrompt::class);\n $mockPromptModel->method('getUuid')->willReturn('prompt-uuid');\n $mockPromptModel->method('getTitle')->willReturn('My Prompt');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getCustomName')->willReturn('Test Report');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getExpiresAt')->willReturn(IlluminateCarbon::parse('2025-12-31'));\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn(['users' => []]);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(1);\n $mockReport->method('getSavedSearch')->willReturn($mockSavedSearch);\n $mockReport->method('getAskAnythingPrompt')->willReturn($mockPromptModel);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUserOrderedByName')->willReturn(new Collection([]));\n\n $mockPromptService = $this->createMock(AskAnythingPromptService::class);\n $mockPromptService->method('get')->willReturn([]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('getAllByTeam')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->method('getRecipientsFieldData')->willReturn(['options' => []]);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $mockRecipientsService,\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $mockPromptService,\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->getAskJiminnyReportFormData($mockUser, $mockReport);\n\n $fields = collect($result['fields'])->keyBy('id');\n\n $this->assertTrue($fields['enabled']['value']);\n $this->assertEquals('Test Report', $fields['report_name']['value']);\n }\n\n public function testValidateAskJiminnyReportDataMissingName(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name is required');\n\n $service->createAskJiminnyReport(['report_name' => ''], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataNameTooLong(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Report name must be 50 characters or less');\n\n $service->createAskJiminnyReport(['report_name' => str_repeat('a', 51)], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataInvalidFrequency(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Frequency must be daily, weekly, or monthly');\n\n $service->createAskJiminnyReport(['report_name' => 'Valid Name', 'frequency' => 'quarterly'], $mockUser);\n }\n\n public function testValidateAskJiminnyReportDataMissingExpiresOn(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresInPast(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be in the past');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2020-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresTooFar(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Expiration date cannot be more than 1 year from now');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => '2099-01-01'],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataExpiresExactlyOneYearLaterTimeOfDayIsAccepted(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-20 09:00:00'));\n\n try {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getId')->willReturn(1);\n $mockUser->method('getTeamId')->willReturn(1);\n\n $savedSearch = $this->createMock(Search::class);\n $savedSearch->method('getId')->willReturn(10);\n\n $prompt = $this->createMock(AskAnythingPrompt::class);\n $prompt->method('getId')->willReturn(5);\n\n $activitySearchRepository = $this->createMock(SearchRepository::class);\n $activitySearchRepository->method('findByUuidAndUser')->willReturn($savedSearch);\n\n $askAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $askAnythingRepository->method('getPromptByUuid')->willReturn($prompt);\n\n $automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);\n $automatedReportsRepository->method('create')->willReturn($this->createMock(AutomatedReport::class));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $automatedReportsRepository,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $activitySearchRepository,\n $askAnythingRepository,\n );\n\n $service->createAskJiminnyReport([\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => '2027-04-20T23:00:00',\n 'saved_search' => 'some-uuid',\n 'ask_jiminny_prompt' => 'prompt-uuid',\n ], $mockUser);\n\n $this->assertTrue(true);\n } finally {\n Carbon::setTestNow();\n }\n }\n\n public function testValidateAskJiminnyReportDataMissingSavedSearch(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search is required');\n\n $service->createAskJiminnyReport(\n ['report_name' => 'Valid Name', 'frequency' => 'daily', 'expires_on' => now()->addMonth()->toDateString()],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataSavedSearchNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Saved search not found or does not belong to you');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'non-existent-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataMissingPrompt(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt is required');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n ],\n $mockUser\n );\n }\n\n public function testValidateAskJiminnyReportDataPromptNotFound(): void\n {\n $mockUser = $this->createMock(User::class);\n\n $mockSearch = $this->createMock(Search::class);\n $mockSearchRepository = $this->createMock(SearchRepository::class);\n $mockSearchRepository->method('findByUuidAndUser')->willReturn($mockSearch);\n\n $mockAskAnythingRepository = $this->createMock(AskAnythingRepository::class);\n $mockAskAnythingRepository->method('getPromptByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $mockSearchRepository,\n $mockAskAnythingRepository,\n );\n\n $this->expectException(InvalidArgumentException::class);\n $this->expectExceptionMessage('Ask Jiminny prompt not found');\n\n $service->createAskJiminnyReport(\n [\n 'report_name' => 'Valid Name',\n 'frequency' => 'daily',\n 'expires_on' => now()->addMonth()->toDateString(),\n 'saved_search' => 'search-uuid',\n 'ask_jiminny_prompt' => 'non-existent-prompt-uuid',\n ],\n $mockUser\n );\n }\n\n public function testTransformRecipientsWithNullUsers(): void\n {\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($this->service, []);\n\n $this->assertEquals([], $result);\n }\n\n public function testTransformRecipientsWithUsersKey(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockUser->method('getUuid')->willReturn('user-uuid-1');\n $mockUser->method('getName')->willReturn('User One');\n $mockUser->method('getEmailAddress')->willReturn('user1@test.com');\n $mockUser->method('getPhotoUrl')->willReturn(null);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($mockUser);\n\n $service = $this->getService(mockUserRepository: $mockUserRepository);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $method = $reflection->getMethod('transformRecipients');\n $method->setAccessible(true);\n\n $result = $method->invoke($service, ['users' => [1]]);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user-uuid-1', $result[0]['id']);\n }\n\n public function testGetTeamsGroupsOptions(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam->method('getName')->willReturn('Sales Team');\n $mockTeam->method('hasFeature')\n ->with(FeatureEnum::AUTOMATED_REPORTS)\n ->willReturn(true);\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam]));\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions();\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n $this->assertArrayHasKey('groups', $result[0]);\n }\n\n public function testGetTeamsGroupsOptionsWithFilter(): void\n {\n $mockTeamRepository = $this->createMock(TeamRepository::class);\n\n $mockTeam1 = $this->createMock(Team::class);\n $mockTeam1->method('getUuid')->willReturn('team-uuid-1');\n $mockTeam1->method('getName')->willReturn('Sales Team');\n $mockTeam1->method('hasFeature')->willReturn(true);\n\n $mockTeam2 = $this->createMock(Team::class);\n $mockTeam2->method('getUuid')->willReturn('team-uuid-2');\n $mockTeam2->method('getName')->willReturn('Marketing Team');\n $mockTeam2->method('hasFeature')->willReturn(true);\n\n $mockTeamRepository->method('getTeamsForKiosk')\n ->willReturn(new Collection([$mockTeam1, $mockTeam2]));\n\n $mockGroupsRelation = $this->createMock(HasMany::class);\n $mockGroupsRelation->method('get')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([]));\n $mockTeam1->method('groups')->willReturn($mockGroupsRelation);\n\n $mockTeamRepository->method('idOrUuid')->willReturn($mockTeam1);\n\n $service = $this->getService(mockTeamRepository: $mockTeamRepository);\n\n $result = $service->getTeamsGroupsOptions(['team-uuid-1']);\n\n $this->assertCount(1, $result);\n $this->assertEquals('Sales Team', $result[0]['label']);\n }\n\n public function testGetReturnsTransformedReport(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('findByUuid')\n ->with('report-uuid')\n ->willReturn($mockReport);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->get('report-uuid');\n\n $this->assertIsArray($result);\n $this->assertEquals('report-uuid', $result['id']);\n }\n\n public function testGetThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->get('missing-uuid');\n }\n\n public function testListReturnsData(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('exec_summary');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('weekly');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getFrom')->willReturn(null);\n $mockReport->method('getTo')->willReturn(null);\n $mockReport->method('getDealValueMin')->willReturn(null);\n $mockReport->method('getDealValueMax')->willReturn(null);\n $mockReport->method('getCallTypes')->willReturn([]);\n $mockReport->method('getMediaTypes')->willReturn([]);\n $mockReport->method('getCallDurationMin')->willReturn(null);\n $mockReport->method('getCallDurationMax')->willReturn(null);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getDealAtCallStages')->willReturn([]);\n $mockReport->method('getCurrentDealStages')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCreator')->willReturn(null);\n $mockReport->method('getAdditionalPromptInput')->willReturn(null);\n $mockReport->method('getCustomName')->willReturn('My Report');\n $mockReport->method('getCreatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getUpdatedAt')->willReturn(IlluminateCarbon::parse('2025-01-01'));\n $mockReport->method('getDeletedAt')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->list();\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testListAskJiminnyReportsReturnsData(): void\n {\n $mockUser = $this->createMock(User::class);\n $mockTeam = $this->createMock(Team::class);\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getType')->willReturn('ask_jiminny');\n $mockReport->method('getUuid')->willReturn('report-uuid');\n $mockReport->method('getFrequency')->willReturn('daily');\n $mockReport->method('getTeam')->willReturn($mockTeam);\n $mockReport->method('getStatus')->willReturn(true);\n $mockReport->method('getGroups')->willReturn([]);\n $mockReport->method('getRecipients')->willReturn([]);\n $mockReport->method('getCustomName')->willReturn('AJ Report');\n $mockReport->method('getExpiresAt')->willReturn(null);\n $mockReport->method('getSavedSearch')->willReturn(null);\n $mockReport->method('getAskAnythingPrompt')->willReturn(null);\n $mockReport->method('getAttribute')->with('created_by')->willReturn(null);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($mockUser, 'created_at', 'desc')\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->listAskJiminnyReports($mockUser);\n\n $this->assertArrayHasKey('data', $result);\n $this->assertCount(1, $result['data']);\n }\n\n public function testGetActivityTypesFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockActivityTypeService = $this->createMock(ActivityTypeService::class);\n $mockActivityTypeService->expects($this->once())\n ->method('getActivityTypeFieldData')\n ->with(team: $mockTeam, value: ['a'], groupIds: ['g1'])\n ->willReturn(['id' => 'activity_types', 'options' => []]);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('activityTypeService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockActivityTypeService);\n\n $result = $service->getActivityTypesFieldData($mockTeam, ['a'], ['g1']);\n\n $this->assertEquals(['id' => 'activity_types', 'options' => []], $result);\n }\n\n public function testGetDealStageAtCallFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getDealStageAtCallFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'deal_stage_at_call']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getDealStageAtCallFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'deal_stage_at_call'], $result);\n }\n\n public function testGetCurrentDealStageFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockDealStagesService = $this->createMock(DealStagesService::class);\n $mockDealStagesService->expects($this->once())\n ->method('getCurrentDealStageFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'current_deal_stage']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('dealStagesService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockDealStagesService);\n\n $result = $service->getCurrentDealStageFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'current_deal_stage'], $result);\n }\n\n public function testGetRecipientsFieldDataDelegatesToService(): void\n {\n $mockTeam = $this->createMock(Team::class);\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getRecipientsFieldData')\n ->with(team: $mockTeam, value: [])\n ->willReturn(['id' => 'recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getRecipientsFieldData($mockTeam);\n\n $this->assertEquals(['id' => 'recipients'], $result);\n }\n\n public function testGetJiminnyRecipientsFieldDataDelegatesToService(): void\n {\n $mockRecipientsService = $this->createMock(RecipientsService::class);\n $mockRecipientsService->expects($this->once())\n ->method('getJiminnyRecipientsFieldData')\n ->with(['user-1'])\n ->willReturn(['id' => 'jiminny_recipients']);\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n $prop = $reflection->getProperty('recipientsService');\n $prop->setAccessible(true);\n $prop->setValue($service, $mockRecipientsService);\n\n $result = $service->getJiminnyRecipientsFieldData(['user-1']);\n\n $this->assertEquals(['id' => 'jiminny_recipients'], $result);\n }\n\n public function testCreateReportResultDelegatesToRepository(): void\n {\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(42);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('createResult')\n ->willReturn($mockResult);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $result = $service->createReportResult($mockReport);\n\n $this->assertSame($mockResult, $result);\n }\n\n public function testDeleteReportResultDeletesS3AndModel(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $reflection = new \\ReflectionClass(AutomatedReportsService::class);\n $service = $reflection->newInstanceWithoutConstructor();\n\n foreach ([\n 'teamRepository' => TeamRepository::class,\n 'groupRepository' => GroupRepository::class,\n 'userRepository' => UserRepository::class,\n 'stageRepository' => StageRepository::class,\n 'dealStagesService' => DealStagesService::class,\n 'recipientsService' => RecipientsService::class,\n 'automatedReportsRepository' => AutomatedReportsRepository::class,\n 'webhookService' => Webhook::class,\n 'dispatcher' => Dispatcher::class,\n 'activityTypeService' => ActivityTypeService::class,\n 'playbookCategoryRepository' => PlaybookCategoryRepository::class,\n 'askAnythingPromptService' => AskAnythingPromptService::class,\n 'activitySearchRepository' => SearchRepository::class,\n 'askAnythingRepository' => AskAnythingRepository::class,\n ] as $propName => $class) {\n $prop = $reflection->getProperty($propName);\n $prop->setAccessible(true);\n $prop->setValue($service, $this->createMock($class));\n }\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteReportResult($mockResult);\n }\n\n public function testDeleteAllReportResultsIteratesAndDeletes(): void\n {\n $mockResult1 = $this->createMock(AutomatedReportResult::class);\n $mockResult1->method('getId')->willReturn(1);\n $mockResult1->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult1->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult1]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllReportResults($mockReport);\n }\n\n public function testDeleteAllDataDeletesReportsAndResults(): void\n {\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getId')->willReturn(1);\n $mockResult->method('getReport')->willReturn($this->createMock(AutomatedReport::class));\n $mockResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);\n $mockResult->expects($this->once())->method('delete');\n\n $mockReport = $this->createMock(AutomatedReport::class);\n $mockReport->method('getId')->willReturn(10);\n $mockReport->expects($this->once())->method('delete');\n\n $mockTeam = $this->createMock(Team::class);\n $mockTeam->method('getId')->willReturn(1);\n\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->expects($this->once())\n ->method('getReportsByTeam')\n ->with($mockTeam)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockReport]));\n $mockRepo->expects($this->once())\n ->method('getResultsByReport')\n ->with($mockReport)\n ->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$mockResult]));\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n Storage::shouldReceive('exists')->andReturn(false);\n Log::shouldReceive('info')->zeroOrMoreTimes();\n\n $service->deleteAllData($mockTeam);\n }\n\n public function testDeleteReportResultsThrowsWhenReportNotFound(): void\n {\n $mockRepo = $this->createMock(AutomatedReportsRepository::class);\n $mockRepo->method('findByUuid')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $this->createMock(UserRepository::class),\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $mockRepo,\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $this->expectException(ModelNotFoundException::class);\n\n $service->deleteReportResults('missing-uuid');\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('creator@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyDeduplicatesCreatorAndExplicitRecipient(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('shared@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => [1]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersAskJiminnyIncludesGroupMembers(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $creator = $this->createMock(User::class);\n $creator->method('getEmailAddress')->willReturn('creator@test.com');\n $creator->method('getName')->willReturn('Creator');\n $creator->method('getTimezone')->willReturn($tz);\n\n $member = $this->createMock(User::class);\n $member->method('getEmailAddress')->willReturn('member@test.com');\n $member->method('getName')->willReturn('Member');\n $member->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($creator);\n\n $mockGroup = $this->createMock(Group::class);\n $mockGroup->method('getMembers')->willReturn(new \\Illuminate\\Database\\Eloquent\\Collection([$member]));\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn($mockGroup);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn($creator);\n $report->method('getRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([10]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(2, $result);\n $emails = array_column($result, 'email');\n $this->assertContains('creator@test.com', $emails);\n $this->assertContains('member@test.com', $emails);\n }\n\n public function testGetValidRecipientUsersAskJiminnyNullCreatorSkipped(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $shareUser = $this->createMock(User::class);\n $shareUser->method('getEmailAddress')->willReturn('shared@test.com');\n $shareUser->method('getName')->willReturn('Shared');\n $shareUser->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturnMap([\n [1, null],\n [2, $shareUser],\n ]);\n\n $mockGroupRepository = $this->createMock(GroupRepository::class);\n $mockGroupRepository->method('find')->willReturn(null);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $mockGroupRepository,\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(true);\n $report->method('getCreator')->willReturn(null);\n $report->method('getRecipients')->willReturn(['users' => [2]]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('shared@test.com', $result[0]['email']);\n }\n\n public function testGetValidRecipientUsersStandardReportDoesNotIncludeCreator(): void\n {\n $tz = $this->createMock(\\DateTimeZone::class);\n $tz->method('getName')->willReturn('UTC');\n\n $user = $this->createMock(User::class);\n $user->method('getEmailAddress')->willReturn('user@test.com');\n $user->method('getName')->willReturn('User');\n $user->method('getTimezone')->willReturn($tz);\n\n $mockUserRepository = $this->createMock(UserRepository::class);\n $mockUserRepository->method('find')->willReturn($user);\n\n $service = new AutomatedReportsService(\n $this->createMock(TeamRepository::class),\n $this->createMock(GroupRepository::class),\n $mockUserRepository,\n $this->createMock(StageRepository::class),\n $this->createMock(DealStagesService::class),\n $this->createMock(RecipientsService::class),\n $this->createMock(AutomatedReportsRepository::class),\n $this->createMock(Webhook::class),\n $this->createMock(Dispatcher::class),\n $this->createMock(ActivityTypeService::class),\n $this->createMock(PlaybookCategoryRepository::class),\n $this->createMock(AskAnythingPromptService::class),\n $this->createMock(SearchRepository::class),\n $this->createMock(AskAnythingRepository::class),\n );\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('isAskJiminnyReport')->willReturn(false);\n $report->method('getRecipients')->willReturn(['users' => [5]]);\n $report->method('getJiminnyRecipients')->willReturn(['users' => []]);\n $report->method('getGroups')->willReturn([]);\n\n $result = $service->getValidRecipientUsers($report);\n\n $this->assertCount(1, $result);\n $this->assertEquals('user@test.com', $result[0]['email']);\n }\n\n public function testGetReportPeriodNameAskJiminnyMonthlyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-03-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertMatchesRegularExpression('/^[A-Z][a-z]+ \\d{4}$/', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWeeklyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('weekly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyDailyFallback(): void\n {\n Carbon::setTestNow(Carbon::parse('2026-04-07 00:00:00'));\n\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('daily');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(null);\n $mockResult->method('getToDate')->willReturn(null);\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertStringNotContainsString(' - ', $result);\n $this->assertStringContainsString('2026', $result);\n\n Carbon::setTestNow();\n }\n\n public function testGetReportPeriodNameAskJiminnyWithExplicitDates(): void\n {\n $report = $this->createMock(AutomatedReport::class);\n $report->method('getFrequency')->willReturn('monthly');\n $report->method('isAskJiminnyReport')->willReturn(true);\n\n $mockResult = $this->createMock(AutomatedReportResult::class);\n $mockResult->method('getReport')->willReturn($report);\n $mockResult->method('getFromDate')->willReturn(IlluminateCarbon::parse('2026-02-07'));\n $mockResult->method('getToDate')->willReturn(IlluminateCarbon::parse('2026-03-07'));\n\n $result = $this->service->getReportPeriodName($mockResult);\n\n $this->assertEquals('Feb 2026', $result);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.24335106,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-680464163007607990
|
-404082269151709463
|
visual_change
|
accessibility
|
NULL
|
The Hunspell plugin has been deprecated. If you The Hunspell plugin has been deprecated. If you're not writing in Hungarian, you can safely uninstall Hunspell without affecting the dictionaries for other languages.
text/html
text/html
text/html
Loading Data Sources…
Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net...
|
NULL
|
|
53465
|
1156
|
19
|
2026-04-20T08:13:44.957492+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776672824957_m1.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEvent.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests passed: 7
text/html
text/html
text/html
Proj Tests passed: 7
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
class TrackAutomatedReportGeneratedEvent implements ShouldQueue
{
use InteractsWithQueue;
private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';
private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';
public string $queue = Constants::QUEUE_DELAYABLE;
public function __construct(
private readonly UserPilotClient $userPilotClient,
private readonly AutomatedReportsService $automatedReportsService,
) {
}
public function handle(AutomatedReportGenerated $event): void
{
if (config('services.userpilot.token') === null) {
return;
}
$automatedReport = $event->automatedReport;
$payload = $this->buildPayload($automatedReport);
$eventName = $this->resolveEventName($automatedReport);
try {
foreach ($this->resolveUsers($automatedReport) as $user) {
$this->userPilotClient->track($user, $eventName, $payload);
}
} catch (GuzzleException $e) {
$this->release(3600);
}
}
/**
* @return array<UserContract>
*/
private function resolveUsers(AutomatedReport $automatedReport): array
{
if ($automatedReport->isAskJiminnyReport()) {
$creator = $automatedReport->getCreator();
return $creator !== null ? [$creator] : [];
}
return $this->automatedReportsService->getRecipientUserObjects($automatedReport);
}
private function buildPayload(AutomatedReport $automatedReport): array
{
return [
'report_type' => $automatedReport->getType(),
'frequency' => $automatedReport->getFrequency(),
];
}
private function resolveEventName(AutomatedReport $automatedReport): string
{
if ($automatedReport->isAskJiminnyReport()) {
return self::EVENT_NAME_ASK_JIMINNY_REPORT;
}
return self::EVENT_NAME_AUTOMATED_REPORT;
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
27
9
23
3
105
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM team_features where team_id = 1;
SELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922
SELECT * FROM users WHERE team_id = 340; # 12015
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 340
and sa.provider = 'salesforce';
# and sa.provider = 'salesloft';
select * from crm_fields where crm_configuration_id = 270 and object_type = 'event';
# 125558 - Event Type - Event_Type__c
# 125552 - Event Status - Event_Status__c
SELECT * FROM sidekick_settings WHERE team_id = 340;
SELECT * FROM crm_field_values WHERE crm_field_id in (125552);
select * from activities where crm_configuration_id = 270
and type = 'conference' and crm_provider_id IS NOT NULL
and actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;
SELECT * FROM activities WHERE id = 20871677;
SELECT * FROM crm_field_data WHERE activity_id = 20871677;
select * from crm_layouts where crm_configuration_id = 270;
select * from crm_layout_entities where crm_layout_id in (886,887);
SELECT * FROM crm_configurations WHERE id = 270;
select * from playbooks where team_id = 340; # 1514
select * from groups where team_id = 340;
SELECT * FROM crm_fields WHERE id IN (125393, 125401);
select g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g
join playbooks p on g.playbook_id = p.id
join crm_fields f on p.activity_field_id = f.id
where g.team_id = 340;
SELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716
select * from crm_field_data where object_id = 20448716;
select * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008
select * from opportunities where team_id = 343;
select * from opportunities where team_id = 343 and crm_provider_id = '18099102526';
select * from opportunities where team_id = 343 and account_id = 945217482;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
select * from accounts where team_id = 343 order by name asc;
select * from stages where crm_configuration_id = 273 and type = 'opportunity';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143
SELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;
SELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';
SELECT * FROM activities WHERE id = 20717903;
select * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 353
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;
# id: 20940638, user: 12022, contact: 5305871
SELECT * FROM activity_summary_logs WHERE activity_id = 20940638;
select * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 345
and sa.provider = 'hubspot';
select * from users where team_id = 345 and id = 12022;
SELECT * FROM crm_profiles WHERE user_id = 12022;
SELECT * FROM participants WHERE activity_id = 20940638;
SELECT * FROM users u
JOIN crm_profiles cp ON u.id = cp.user_id
WHERE u.team_id = 345;
select * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871
select * from team_features where team_id = 345;
SELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197
SELECT * FROM participants WHERE activity_id = 20897406;
SELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912
SELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';
SELECT * FROM activities WHERE id = 20946641;
SELECT * FROM crm_profiles WHERE user_id = 10211;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, [EMAIL]
SELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';
select * from stages where crm_configuration_id = 97 and type = 'opportunity';
select * from opportunities where team_id = 120;
select * from crm_configurations crm join teams t on crm.id = t.crm_id
where 1=1
AND t.current_billing_plan IS NOT NULL
AND crm.auto_sync_activity = 0
and crm.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 270
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956
SELECT * FROM crm_profiles WHERE user_id = 11446;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, [EMAIL]
select * from playbooks where team_id = 372;
select * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340
SELECT * FROM crm_field_values WHERE crm_field_id = 141340;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 372
and sa.provider = 'salesforce';
select * from crm_profiles where crm_configuration_id = 300;
SELECT * FROM crm_configurations WHERE team_id = 372;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,[EMAIL]
SELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756
select * from crm_field_data where object_id = 3207756;
SELECT * FROM crm_fields WHERE id = 111834;
select f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value
FROM crm_fields f
JOIN crm_field_data fd ON f.id = fd.crm_field_id
WHERE f.crm_configuration_id = 242
AND f.object_type = 'opportunity'
AND fd.object_id IN (3207756)
ORDER BY fd.object_id, fd.updated_at;
SELECT * FROM crm_configurations WHERE auto_connect = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,[EMAIL]
select * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id
where g.team_id = 187;
select * from `groups` where team_id = 187;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 187
and sa.provider = 'salesforce';
# Destination - 98870 - Destination__c
# Stage - 79014 - StageName
# Land Arrangement - 98856 - Land_Arrangement__c
# Flight - 98848 - Flight__c
# Last activity date - 98812 - LastActivityDate
# Last modified date - 98809 - LastModifiedDate
# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c
# next call - 98864 - Next_Call__c
select * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
select * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';
select * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;
select * from activities where opportunity_id = 3538248;
SELECT * FROM crm_profiles WHERE user_id = 8150;
select * from deal_risks where opportunity_id = 3538248;
select * from teams where crm_id IS NULL;
SELECT opp.id AS opportunity_id,
u.group_id AS group_id,
MAX(
CASE
WHEN a.type IN ("sms-inbound", "sms-outbound") THEN a.created_at
ELSE a.actual_end_time
END) as last_date
FROM opportunities opp
left join activities a on a.opportunity_id = opp.id
inner join users u on opp.user_id = u.id
where opp.user_id IN (9951)
AND opp.is_closed = 0
and a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL
group by opp.id;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_profiles WHERE crm_configuration_id = 301;
SELECT * FROM contacts WHERE id = 6612363;
SELECT * FROM accounts WHERE id = 4235676;
SELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;
select * from opportunity_stages where opportunity_id = 4503759;
# SELECT * FROM opportunities WHERE id = 4569937;
select * from activities where crm_configuration_id = 301;
SELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370
SELECT * FROM participants WHERE activity_id = 26330370;
SELECT * FROM teams WHERE id = 375;
select * from playbooks where team_id = 375;
select * from stages where crm_configuration_id = 301 and type = 'opportunity';
select * from teams;
select * from contact_roles;
SELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';
select * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;
SELECT * FROM crm_field_data WHERE object_id = 3771706;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'
and crm_provider_id LIKE "%traffic_light%";
SELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);
SELECT fd.* FROM opportunities o
JOIN crm_field_data fd ON o.id = fd.object_id
WHERE o.team_id = 343
# and o.user_id IS NOT NULL
and fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)
and fd.value != ''
order by value desc
# group by o.id
;
SELECT * FROM opportunities WHERE id = 3769843;
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, [EMAIL]
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,[EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839
SELECT * FROM opportunities WHERE id = 3855992;
SELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988
SELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894
SELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';
select * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507
SELECT * FROM crm_field_data WHERE object_id = 5874411;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379
and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793
select * from generic_ai_prompts where subject_id = 3537793;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, [EMAIL]
SELECT * FROM crm_configurations WHERE id = 97;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 97;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;
SELECT * FROM crm_fields WHERE id = 32682;
select cfd.value, o.* from opportunities o
join crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682
where team_id = 120
and cfd.value != ''
;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 120
and sa.provider = 'salesforce';
select * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';
SELECT * FROM crm_field_data WHERE object_id = 2313439;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 410;
SELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';
select * from scorecards where team_id = 410;
select * from scorecard_rules;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, [EMAIL]
select * from activities a
join opportunities o on a.opportunity_id = o.id
join users u on o.user_id = u.id
where a.crm_configuration_id = 177 and a.type LIKE '%email-out%'
# and a.actual_end_time > '2024-12-16 00:00:00'
# and o.remotely_created_at > '2024-12-01 00:00:00'
# and u.group_id = 1014
and u.id = 9021
order by a.id desc;
SELECT * FROM opportunities WHERE id in (3981384,4017346);
SELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);
select * from users where id = 9021;
select * from inboxes where user_id = 9021;
select * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';
select * from email_messages where team_id = 220
and orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'
and subject LIKE '%Personal%'
# and 'from' = '[EMAIL]'
;
select * from activities a
join opportunities o on a.opportunity_id = o.id
where a.user_id = 9021 and a.type LIKE '%email-out%'
and a.actual_end_time > '2024-12-18 00:00:00'
and o.user_id IS NOT NULL
and o.remotely_created_at > '2024-12-01 00:00:00'
order by a.id desc;
SELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;
select * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;
select * from team_settings where name IN ('useCloseDate');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 104
and sa.provider = 'hubspot';
select * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'
select * from teams where crm_id IS NULL;
select t.name as 'team', u.name as 'owner', u.email, u.phone
from teams t
join activity_providers ap on t.id = ap.team_id
join users u on t.owner_id = u.id
where 1=1
and t.status = 'active'
and ap.is_enabled = 1
# and u.status = 1
and ap.provider = 'ms-teams';
select * from crm_configurations where provider = 'bullhorn'; # 344
SELECT * FROM teams WHERE id = 442; # 14293
select * from users where team_id = 442;
select * from social_accounts sa where sa.sociable_id = 14293;
select * from invitations where team_id = 442;
# [PASSWORD_DOTS]
SELECT * FROM users WHERE email LIKE '%[EMAIL]%'; # 14022
SELECT * FROM teams WHERE id = 429;
select * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);
select * from activities where opportunity_id in (4340436,4353519);
select * from transcription where activity_id IN (25630961,25381771);
select * from generic_ai_prompts where subject_id IN (4353519);
SELECT
a.id as activity_id,
a.opportunity_id,
a.type as activity_type,
a.language,
CONCAT(a.title, a.description) AS mail_content,
e.from AS mail_from,
e.to AS mail_to,
e.subject AS mail_subject,
e.body AS mail_body,
p.type as prompt_type,
p.status as prompt_status,
p.content AS prompt_content,
a.actual_start_time as created_at
FROM activities a
LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL
LEFT JOIN email_messages e ON a.id = e.activity_id
WHERE a.actual_start_time > '2024-01-01 00:00:00'
AND a.opportunity_id IN (4353519)
AND a.status IN ('completed', 'received', 'delivered')
AND a.deleted_at IS NULL
AND a.type NOT IN ('sms-inbound', 'sms-outbound')
ORDER BY a.opportunity_id ASC, a.id ASC;
SELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293
SELECT * FROM teams WHERE id = 442;
SELECT * FROM crm_configurations WHERE id = 344;
select * from team_features where team_id = 442;
select * from groups where team_id = 442;
select * from playbooks where team_id = 442;
select * from playbook_categories where playbook_id = 1729;
select * from crm_fields where crm_configuration_id = 344 and id = 172024;
SELECT * FROM crm_field_values WHERE crm_field_id = 172024;
select * from crm_layouts where crm_configuration_id = 344;
select * from playbook_layouts where playbook_id = 1729;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444
select s.*
# , s.sent_at, u.name, a.*
from activity_summary_logs s
inner join activities a on a.id = s.activity_id
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 356
and s.sent_at > date_sub(now(), interval 60 day)
order by a.actual_end_time desc;
select * from activities a
# inner join activity_summary_logs s on s.activity_id = a.id
where a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)
# and a.crm_provider_id is not null
# and provider <> 'ringcentral'
and status = 'completed'
order by a.actual_end_time desc;
select * from teams order by id desc; # 17328, 32, 17830, [EMAIL]
SELECT * FROM users;
SELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active
SELECT * FROM teams WHERE id = 260;
select * from team_settings where team_id = 260;
select * from crm_configurations where team_id = 260;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 356;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;
select * from accounts where crm_configuration_id = 221 order by id desc; # 7000
select * from leads where crm_configuration_id = 221 order by id desc; # 0
select * from contacts where crm_configuration_id = 221 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 221 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 221;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 221 order by id desc;
select * from stages where crm_configuration_id = 221 order by id desc;
select * from accounts where crm_configuration_id = 356 order by id desc; # 7000
select * from leads where crm_configuration_id = 356 order by id desc; # 0
select * from contacts where crm_configuration_id = 356 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 356 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 356;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 356 order by id desc;
select * from stages where crm_configuration_id = 356 order by id desc;
select * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)
select * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)
select * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4
select ce.* from calendars c
join users u on c.user_id = u.id
join calendar_events ce on c.id = ce.calendar_id
where u.team_id = 260
and (ce.start_time > '2025-02-21 00:00:00')
;
# calendar events 1207
#
select * from opportunities where team_id = 260;
SELECT * FROM crm_field_data WHERE object_id = 4696496;
select * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;
select * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')
# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0
and created_at > '2024-03-01 00:00:00'
order by id desc; # 880 000, ringcentral, avaya
SELECT * FROM participants WHERE activity_id = 26371744;
# all activities 942 000 +
# conference 7385 - scheduled 984 - external 343
select * from activities where id = 26321812;
select * from participants where activity_id = 26321812;
select * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);
select * from leads where id in (720428,689175,731546,645866,621037);
select * from users where id = 13841;
select * from opportunities where user_id = 9541;
select * from stages where id = 15900;
select * from accounts where
# id IN (4160055,5053725,4965303,4896434)
id in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)
;
select * from activities where id = 26654935;
SELECT * FROM opportunities WHERE id = 4803458;
SELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;
SELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time
FROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);
SELECT DISTINCT
o.id, o.stage_id, s.name, a.title,
a.*
FROM activities a
# INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
INNER JOIN groups g ON u.group_id = g.id
INNER JOIN opportunities o ON a.opportunity_id = o.id
INNER JOIN stages s ON o.stage_id = s.id
WHERE
a.crm_configuration_id = 356
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 13841
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')
AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
)
)
AND (
# s.id = 15900
s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')
OR s.uuid IS NULL -- Include records without opportunity stage
)
ORDER BY a.actual_end_time DESC;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, [EMAIL]
SELECT * FROM users WHERE team_id = 190;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 190
and sa.provider = 'hubspot';
select * from role_user where user_id = 8474;
select * from crm_configurations where provider = 'bullhorn';
SELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;
SELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;
SELECT * FROM opportunities WHERE id = 4732493;
select * from activities where opportunity_id = 4732493;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 443; # 358, 14315, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 443;
SELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id
FROM activities AS a
JOIN stages AS s ON a.stage_id = s.id
JOIN users AS u ON u.id = a.user_id
JOIN teams AS t ON t.id = s.team_id
WHERE u.team_id <> s.team_id and t.id > 135;
SELECT
crm_configuration_id,
crm_provider_id,
COUNT(*) as duplicate_count,
GROUP_CONCAT(id) as stage_ids,
GROUP_CONCAT(name) as stage_names
FROM stages
GROUP BY crm_configuration_id, crm_provider_id
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC;
select * from stages where id IN (14898,14907);
select * from business_processes;
SELECT *
FROM crm_configurations
WHERE team_id IN (
SELECT team_id
FROM crm_configurations
GROUP BY team_id
HAVING COUNT(*) > 1
)
ORDER BY team_id;
SELECT *
FROM teams
WHERE crm_id IN (
SELECT crm_id
FROM teams
GROUP BY crm_id
HAVING COUNT(*) > 1
)
ORDER BY crm_id;
# [PASSWORD_DOTS]
select * from crm_configurations where provider = 'integration-app';
SELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 [EMAIL]
select * from activities where crm_configuration_id = 358 order by actual_end_time desc;
select id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;
select * from team_features where team_id = 358;
select * from activity_summary_logs;
select * from teams where id = 406;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, [EMAIL]
select * from activities where crm_configuration_id = 202 order by actual_end_time desc;
SELECT * FROM users where id = 14637;
SELECT * FROM teams where id = 267;
SELECT * FROM groups where id = 1118;
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM activities
WHERE crm_configuration_id = 202
AND status IN ('completed', 'failed')
AND recording_state != 'stopped'
AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
AND (is_private = 0 OR user_id = 14637)
AND (
(
actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
) OR (
actual_start_time IS NULL
AND type IN ('sms-outbound', 'sms-inbound')
AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND NOT EXISTS (
SELECT 1
FROM tracks
WHERE
tracks.activity_id = activities.id
AND tracks.type IN ('audio', 'video')
)
ORDER BY actual_end_time DESC;
SELECT DISTINCT
a.*
FROM activities a
INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
WHERE
a.crm_configuration_id = 202
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 14637
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND a.user_id = 14637
)
)
ORDER BY a.actual_end_time DESC
;
SELECT DISTINCT a.*
FROM activities a
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams t ON u.team_id = t.id
# INNER JOIN tracks tr ON a.id = tr.activity_id
# INNER JOIN groups g ON u.group_id = g.id
WHERE 1=1
AND t.id = 267
# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND tr.type NOT IN ('audio', 'video')
AND (
a.is_private = 0
OR a.user_id = 14637
)
AND (
(a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')
OR (
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'
)
)
# and NOT EXISTS (
# SELECT 1
# FROM tracks t
# WHERE t.activity_id = a.id
# AND t.type IN ('audio', 'video')
# )
ORDER BY a.actual_end_time DESC;
SELECT * FROM tracks WHERE activity_id = 26485995;
select a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 202
# and a.is_internal = 0
and (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type IN ("softphone","softphone-inbound","conference","sms-inbound")
and a.status IN ('completed', 'failed')
# and a.external_id is not null
order by a.actual_end_time desc;
select * from activities a where a.crm_configuration_id = 202
and a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'
# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM teams WHERE name LIKE '%Tourlane%';
SELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_field_data WHERE crm_field_id = 98809;
select * from users where status = 1 AND timezone = 'MDT';
select * from opportunities where id = 3769814;
select * from deal_risks where opportunity_id = 3769814;
select cp.* from crm_profiles cp
join users u on cp.user_id = u.id
join crm_configurations crm on cp.crm_configuration_id = crm.id
where crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';
select * from crm_fields where id = 154575;
select * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';
SELECT * FROM teams WHERE id = 176; # crm 148
select * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;
select * from activity_providers where provider = 'amazon-connect';
select * from crm_fields cf
join crm_configurations crm on crm.id = cf.crm_configuration_id
where crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');
# [PASSWORD_DOTS]
SELECT * FROM users WHERE id IN (15415, 15418);
SELECT * FROM groups WHERE id IN (1805,1806);
SELECT * FROM playbooks WHERE id = 1860;
SELECT * FROM playbook_categories WHERE id = 38634;
SELECT * FROM crm_fields WHERE id = 189962;
SELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 [EMAIL]
SELECT * FROM crm_profiles WHERE user_id = 15415;
SELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';
select * from sidekick_settings where team_id = 472;
SELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418
SELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, [EMAIL]
select * from crm_configurations where id = 218;
SELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765
SELECT * FROM users WHERE id IN (13232, 13230);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
0057R00000EPL5HQAX Inez Ekblad
1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur
SELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);
############################################################################################
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id IN (94491,94493,94498);
SELECT * FROM users WHERE id = 13658;
SELECT * FROM teams WHERE id = 109;
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, [EMAIL]
SELECT * FROM stages WHERE crm_configuration_id = 390;
select * from business_processes where team_id = 481 and crm_configuration_id = 390;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 481
and sa.provider = 'salesforce';
SELECT * FROM users WHERE id = 15780; # team 462
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 462
and sa.provider = 'hubspot';
select * from teams where id = 495;
SELECT * FROM users WHERE id = 15794;
select * from social_accounts where sociable_id = 15794;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752
SELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794
SELECT * FROM activities WHERE crm_configuration_id = 407
and status = 'completed' and type = 'conference'
order by id desc;
select ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id
join permission_role pr on pr.role_id = ru.role_id
join permissions p on p.id = pr.permission_id
where team_id = 495 and p.name IN ('dial');
select * from permission_role;
select * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;
SELECT * FROM activities WHERE id = 29512773;
SELECT * FROM activities WHERE id IN (29042721,28991325,29002874);
SELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 407
# and a.id IN (29042721,28991325,29002874);
SELECT * FROM users WHERE id = 15794;
SELECT * FROM users WHERE team_id = 495;
SELECT * FROM social_accounts WHERE sociable_id = 15794;
SELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';
SELECT * FROM contacts WHERE team_id = 495;
SELECT * FROM leads WHERE team_id = 495;
SELECT * FROM accounts WHERE team_id = 495;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 407;
SELECT * FROM crm_fields WHERE crm_configuration_id = 407;
SELECT * FROM crm_configurations WHERE id = 407;
SELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'
and user_id IS NOT NULL and is_closed = 1 and is_won = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103
SELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064
SELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 325
and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085
SELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733
SELECT * FROM activity_summary_logs where activity_id = 28719733;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444
SELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';
SELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630
select * from activities where crm_configuration_id = 356 and lead_id = 841732;
SELECT * from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 356;
select * from activities where crm_configuration_id = 356
and actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'
order by id desc;
select * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;
select * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from team_features where team_id = 260;
select * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;
select * from crm_fields;
select * from crm_layout_entities;
SELECT * FROM teams WHERE name LIKE '%Optable%';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id in (94491,94493,94498);
select * from teams where crm_id IS NULL;
SELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;
# [PASSWORD_DOTS]
select * from team_domains where team_id = 399;
SELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207
select * from calendar_events where id = 5163781;
SELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896
SELECT * FROM participants WHERE activity_id = 29443896;
select * from contacts where crm_configuration_id = 318 and email = '[EMAIL]';
select * from leads where crm_configuration_id = 318 and email = '[EMAIL]';
select * from activities where user_id = 14937 order by created_at ;
select * from users where id = 14937;
select * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';
select * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';
select * from activities a join participants p on a.id = p.activity_id
where crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';
# [PASSWORD_DOTS]
SELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';
SELECT * FROM opportunities WHERE team_id = 379 order by id desc;
SELECT * FROM teams WHERE id = 379;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379 and sociable_id = 13852
and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE id = 307;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 307;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;
SELECT * FROM crm_fields WHERE crm_configuration_id = 307
and id IN (144750,144855,145158,155227);
SELECT * FROM activities;
select * from activities
where created_at > '2025-07-01 00:00:00'
# and created_at < '2025-08-01 00:00:00'
and type not in ('email-outbound', 'email-inbound')
and account_id is null
and contact_id is null
and lead_id is null
and opportunity_id is not null
;
SELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);
SELECT * FROM crm_configurations WHERE id in (335,301,200);
select * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';
SELECT * FROM teams WHERE name LIKE '%Resights%';
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_configurations where provider = 'bullhorn'; # 344
select * from teams where id IN (442);
select * from activities
where crm_configuration_id = 177
and provider = 'amazon-connect'
order by id desc;
# and source <> 'gong';
select * from activity_providers where provider = 'amazon-connect';
SELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;
select * from crm_configurations where store_transcript = 1;
SELECT * FROM teams WHERE id IN (80);
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 277
and sa.provider = 'salesforce';
select * from activities where crm_configuration_id = 213 and account_id = 2511502;
select * from crm_configurations where id = 213;
SELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604
SELECT * FROM participants WHERE activity_id = 33981604;
SELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 431
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223
select * from activity_summary_logs where activity_id = 33997223;
select * from activity_notes where activity_id =...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests passed: 7","depth":3,"value":"Tests passed: 7","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\n\nclass TrackAutomatedReportGeneratedEvent implements ShouldQueue\n{\n use InteractsWithQueue;\n\n private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';\n private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';\n\n public string $queue = Constants::QUEUE_DELAYABLE;\n\n public function __construct(\n private readonly UserPilotClient $userPilotClient,\n private readonly AutomatedReportsService $automatedReportsService,\n ) {\n }\n\n public function handle(AutomatedReportGenerated $event): void\n {\n if (config('services.userpilot.token') === null) {\n return;\n }\n\n $automatedReport = $event->automatedReport;\n $payload = $this->buildPayload($automatedReport);\n\n $eventName = $this->resolveEventName($automatedReport);\n\n try {\n foreach ($this->resolveUsers($automatedReport) as $user) {\n $this->userPilotClient->track($user, $eventName, $payload);\n }\n } catch (GuzzleException $e) {\n $this->release(3600);\n }\n }\n\n /**\n * @return array<UserContract>\n */\n private function resolveUsers(AutomatedReport $automatedReport): array\n {\n if ($automatedReport->isAskJiminnyReport()) {\n $creator = $automatedReport->getCreator();\n\n return $creator !== null ? [$creator] : [];\n }\n\n return $this->automatedReportsService->getRecipientUserObjects($automatedReport);\n }\n\n private function buildPayload(AutomatedReport $automatedReport): array\n {\n return [\n 'report_type' => $automatedReport->getType(),\n 'frequency' => $automatedReport->getFrequency(),\n ];\n }\n\n private function resolveEventName(AutomatedReport $automatedReport): string\n {\n if ($automatedReport->isAskJiminnyReport()) {\n return self::EVENT_NAME_ASK_JIMINNY_REPORT;\n }\n\n return self::EVENT_NAME_AUTOMATED_REPORT;\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\n\nclass TrackAutomatedReportGeneratedEvent implements ShouldQueue\n{\n use InteractsWithQueue;\n\n private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';\n private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';\n\n public string $queue = Constants::QUEUE_DELAYABLE;\n\n public function __construct(\n private readonly UserPilotClient $userPilotClient,\n private readonly AutomatedReportsService $automatedReportsService,\n ) {\n }\n\n public function handle(AutomatedReportGenerated $event): void\n {\n if (config('services.userpilot.token') === null) {\n return;\n }\n\n $automatedReport = $event->automatedReport;\n $payload = $this->buildPayload($automatedReport);\n\n $eventName = $this->resolveEventName($automatedReport);\n\n try {\n foreach ($this->resolveUsers($automatedReport) as $user) {\n $this->userPilotClient->track($user, $eventName, $payload);\n }\n } catch (GuzzleException $e) {\n $this->release(3600);\n }\n }\n\n /**\n * @return array<UserContract>\n */\n private function resolveUsers(AutomatedReport $automatedReport): array\n {\n if ($automatedReport->isAskJiminnyReport()) {\n $creator = $automatedReport->getCreator();\n\n return $creator !== null ? [$creator] : [];\n }\n\n return $this->automatedReportsService->getRecipientUserObjects($automatedReport);\n }\n\n private function buildPayload(AutomatedReport $automatedReport): array\n {\n return [\n 'report_type' => $automatedReport->getType(),\n 'frequency' => $automatedReport->getFrequency(),\n ];\n }\n\n private function resolveEventName(AutomatedReport $automatedReport): string\n {\n if ($automatedReport->isAskJiminnyReport()) {\n return self::EVENT_NAME_ASK_JIMINNY_REPORT;\n }\n\n return self::EVENT_NAME_AUTOMATED_REPORT;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"27","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"23","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"105","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT * FROM team_features where team_id = 1;\n\nSELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922\nSELECT * FROM users WHERE team_id = 340; # 12015\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 340\nand sa.provider = 'salesforce';\n# and sa.provider = 'salesloft';\n\nselect * from crm_fields where crm_configuration_id = 270 and object_type = 'event';\n# 125558 - Event Type - Event_Type__c\n# 125552 - Event Status - Event_Status__c\n\nSELECT * FROM sidekick_settings WHERE team_id = 340;\n\nSELECT * FROM crm_field_values WHERE crm_field_id in (125552);\n\nselect * from activities where crm_configuration_id = 270\nand type = 'conference' and crm_provider_id IS NOT NULL\nand actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;\n\nSELECT * FROM activities WHERE id = 20871677;\nSELECT * FROM crm_field_data WHERE activity_id = 20871677;\n\nselect * from crm_layouts where crm_configuration_id = 270;\nselect * from crm_layout_entities where crm_layout_id in (886,887);\n\nSELECT * FROM crm_configurations WHERE id = 270;\n\nselect * from playbooks where team_id = 340; # 1514\nselect * from groups where team_id = 340;\nSELECT * FROM crm_fields WHERE id IN (125393, 125401);\n\nselect g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g\njoin playbooks p on g.playbook_id = p.id\njoin crm_fields f on p.activity_field_id = f.id\nwhere g.team_id = 340;\n\nSELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716\nselect * from crm_field_data where object_id = 20448716;\n\nselect * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008\nselect * from opportunities where team_id = 343;\nselect * from opportunities where team_id = 343 and crm_provider_id = '18099102526';\nselect * from opportunities where team_id = 343 and account_id = 945217482;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from accounts where team_id = 343 order by name asc;\n\nselect * from stages where crm_configuration_id = 273 and type = 'opportunity';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143\nSELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;\nSELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';\nSELECT * FROM activities WHERE id = 20717903;\n\nselect * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 353\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, l.atkinson@mwbsolutions.co.uk\nSELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;\n# id: 20940638, user: 12022, contact: 5305871\nSELECT * FROM activity_summary_logs WHERE activity_id = 20940638;\nselect * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 345\nand sa.provider = 'hubspot';\n\nselect * from users where team_id = 345 and id = 12022;\nSELECT * FROM crm_profiles WHERE user_id = 12022;\nSELECT * FROM participants WHERE activity_id = 20940638;\nSELECT * FROM users u\nJOIN crm_profiles cp ON u.id = cp.user_id\nWHERE u.team_id = 345;\n\nselect * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871\n\nselect * from team_features where team_id = 345;\nSELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197\nSELECT * FROM participants WHERE activity_id = 20897406;\n\n\n\nSELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912\nSELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';\n\n\nSELECT * FROM activities WHERE id = 20946641;\nSELECT * FROM crm_profiles WHERE user_id = 10211;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, triger@lunio.ai\nSELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';\nselect * from stages where crm_configuration_id = 97 and type = 'opportunity';\nselect * from opportunities where team_id = 120;\n\n\nselect * from crm_configurations crm join teams t on crm.id = t.crm_id\nwhere 1=1\nAND t.current_billing_plan IS NOT NULL\nAND crm.auto_sync_activity = 0\nand crm.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,james.lewendon@exclaimer.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 270\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956\nSELECT * FROM crm_profiles WHERE user_id = 11446;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, alex.chikly@cygnetise.com\nselect * from playbooks where team_id = 372;\nselect * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340\nSELECT * FROM crm_field_values WHERE crm_field_id = 141340;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 372\nand sa.provider = 'salesforce';\n\nselect * from crm_profiles where crm_configuration_id = 300;\nSELECT * FROM crm_configurations WHERE team_id = 372;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,mfa@planday.com\nSELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756\nselect * from crm_field_data where object_id = 3207756;\nSELECT * FROM crm_fields WHERE id = 111834;\n\nselect f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value\nFROM crm_fields f\nJOIN crm_field_data fd ON f.id = fd.crm_field_id\nWHERE f.crm_configuration_id = 242\nAND f.object_type = 'opportunity'\nAND fd.object_id IN (3207756)\nORDER BY fd.object_id, fd.updated_at;\n\nSELECT * FROM crm_configurations WHERE auto_connect = 1;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,salesforce-admin@tourlane.com\nselect * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id\nwhere g.team_id = 187;\n\nselect * from `groups` where team_id = 187;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 187\nand sa.provider = 'salesforce';\n\n# Destination - 98870 - Destination__c\n# Stage - 79014 - StageName\n# Land Arrangement - 98856 - Land_Arrangement__c\n# Flight - 98848 - Flight__c\n# Last activity date - 98812 - LastActivityDate\n# Last modified date - 98809 - LastModifiedDate\n# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c\n# next call - 98864 - Next_Call__c\n\nselect * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\nselect * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';\nselect * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;\nselect * from activities where opportunity_id = 3538248;\n\nSELECT * FROM crm_profiles WHERE user_id = 8150;\n\nselect * from deal_risks where opportunity_id = 3538248;\n\nselect * from teams where crm_id IS NULL;\n\nSELECT opp.id AS opportunity_id,\n u.group_id AS group_id,\n MAX(\n CASE\n WHEN a.type IN (\"sms-inbound\", \"sms-outbound\") THEN a.created_at\n ELSE a.actual_end_time\n END) as last_date\nFROM opportunities opp\nleft join activities a on a.opportunity_id = opp.id\ninner join users u on opp.user_id = u.id\nwhere opp.user_id IN (9951)\n\nAND opp.is_closed = 0\nand a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL\ngroup by opp.id;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,polly.morphew@cybsafe.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 301;\nSELECT * FROM contacts WHERE id = 6612363;\nSELECT * FROM accounts WHERE id = 4235676;\nSELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;\nselect * from opportunity_stages where opportunity_id = 4503759;\n# SELECT * FROM opportunities WHERE id = 4569937;\n\nselect * from activities where crm_configuration_id = 301;\nSELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370\nSELECT * FROM participants WHERE activity_id = 26330370;\n\nSELECT * FROM teams WHERE id = 375;\nselect * from playbooks where team_id = 375;\n\nselect * from stages where crm_configuration_id = 301 and type = 'opportunity';\n\nselect * from teams;\nselect * from contact_roles;\n\nSELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';\n\nselect * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;\n\nSELECT * FROM crm_field_data WHERE object_id = 3771706;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'\nand crm_provider_id LIKE \"%traffic_light%\";\nSELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);\n\nSELECT fd.* FROM opportunities o\nJOIN crm_field_data fd ON o.id = fd.object_id\nWHERE o.team_id = 343\n# and o.user_id IS NOT NULL\nand fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)\nand fd.value != ''\norder by value desc\n# group by o.id\n;\n\nSELECT * FROM opportunities WHERE id = 3769843;\n\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, salesforce-admin@tourlane.com\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,aswini.mishra@fundingcircle.com\nSELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839\n\n\nSELECT * FROM opportunities WHERE id = 3855992;\n\nSELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988\n\nSELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';\n\nselect * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507\nSELECT * FROM crm_field_data WHERE object_id = 5874411;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379\nand sa.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, nikhil.kumar@mention-me.com\nSELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, salesforce-admin@tourlane.com\nSELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793\nselect * from generic_ai_prompts where subject_id = 3537793;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, triger@lunio.ai\nSELECT * FROM crm_configurations WHERE id = 97;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 97;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;\nSELECT * FROM crm_fields WHERE id = 32682;\n\nselect cfd.value, o.* from opportunities o\njoin crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682\nwhere team_id = 120\nand cfd.value != ''\n;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 120\nand sa.provider = 'salesforce';\n\nselect * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';\nSELECT * FROM crm_field_data WHERE object_id = 2313439;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 410;\nSELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';\nselect * from scorecards where team_id = 410;\nselect * from scorecard_rules;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, aswini.mishra@fundingcircle.com\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\njoin users u on o.user_id = u.id\nwhere a.crm_configuration_id = 177 and a.type LIKE '%email-out%'\n# and a.actual_end_time > '2024-12-16 00:00:00'\n# and o.remotely_created_at > '2024-12-01 00:00:00'\n# and u.group_id = 1014\nand u.id = 9021\norder by a.id desc;\nSELECT * FROM opportunities WHERE id in (3981384,4017346);\nSELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);\n\nselect * from users where id = 9021;\nselect * from inboxes where user_id = 9021;\n\nselect * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';\n\nselect * from email_messages where team_id = 220\nand orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'\nand subject LIKE '%Personal%'\n# and 'from' = 'credit@fundingcircle.com'\n;\n\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\nwhere a.user_id = 9021 and a.type LIKE '%email-out%'\nand a.actual_end_time > '2024-12-18 00:00:00'\nand o.user_id IS NOT NULL\nand o.remotely_created_at > '2024-12-01 00:00:00'\norder by a.id desc;\n\nSELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;\nselect * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;\n\nselect * from team_settings where name IN ('useCloseDate');\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, jfarrell@hurree.co\nSELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 104\nand sa.provider = 'hubspot';\n\nselect * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'\nselect * from teams where crm_id IS NULL;\n\nselect t.name as 'team', u.name as 'owner', u.email, u.phone\nfrom teams t\njoin activity_providers ap on t.id = ap.team_id\njoin users u on t.owner_id = u.id\nwhere 1=1\n and t.status = 'active'\n and ap.is_enabled = 1\n# and u.status = 1\n and ap.provider = 'ms-teams';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nSELECT * FROM teams WHERE id = 442; # 14293\nselect * from users where team_id = 442;\nselect * from social_accounts sa where sa.sociable_id = 14293;\nselect * from invitations where team_id = 442;\n\n# ********************************************************************************************************\nSELECT * FROM users WHERE email LIKE '%nea.liikamaa@eletive.com%'; # 14022\nSELECT * FROM teams WHERE id = 429;\nselect * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);\nselect * from activities where opportunity_id in (4340436,4353519);\n\nselect * from transcription where activity_id IN (25630961,25381771);\nselect * from generic_ai_prompts where subject_id IN (4353519);\n\nSELECT\n a.id as activity_id,\n a.opportunity_id,\n a.type as activity_type,\n a.language,\n CONCAT(a.title, a.description) AS mail_content,\n e.from AS mail_from,\n e.to AS mail_to,\n e.subject AS mail_subject,\n e.body AS mail_body,\n p.type as prompt_type,\n p.status as prompt_status,\n p.content AS prompt_content,\n a.actual_start_time as created_at\nFROM activities a\n LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL\n LEFT JOIN email_messages e ON a.id = e.activity_id\nWHERE a.actual_start_time > '2024-01-01 00:00:00'\n AND a.opportunity_id IN (4353519)\n AND a.status IN ('completed', 'received', 'delivered')\n AND a.deleted_at IS NULL\n AND a.type NOT IN ('sms-inbound', 'sms-outbound')\nORDER BY a.opportunity_id ASC, a.id ASC;\n\nSELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293\nSELECT * FROM teams WHERE id = 442;\nSELECT * FROM crm_configurations WHERE id = 344;\nselect * from team_features where team_id = 442;\nselect * from groups where team_id = 442;\nselect * from playbooks where team_id = 442;\nselect * from playbook_categories where playbook_id = 1729;\nselect * from crm_fields where crm_configuration_id = 344 and id = 172024;\nSELECT * FROM crm_field_values WHERE crm_field_id = 172024;\nselect * from crm_layouts where crm_configuration_id = 344;\nselect * from playbook_layouts where playbook_id = 1729;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444\n\nselect s.*\n# , s.sent_at, u.name, a.*\nfrom activity_summary_logs s\ninner join activities a on a.id = s.activity_id\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 356\nand s.sent_at > date_sub(now(), interval 60 day)\norder by a.actual_end_time desc;\n\nselect * from activities a\n# inner join activity_summary_logs s on s.activity_id = a.id\nwhere a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)\n# and a.crm_provider_id is not null\n# and provider <> 'ringcentral'\nand status = 'completed'\norder by a.actual_end_time desc;\n\nselect * from teams order by id desc; # 17328, 32, 17830, integration-account@jiminny.com\nSELECT * FROM users;\nSELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active\nSELECT * FROM teams WHERE id = 260;\nselect * from team_settings where team_id = 260;\nselect * from crm_configurations where team_id = 260;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 356;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;\n\nselect * from accounts where crm_configuration_id = 221 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 221 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 221 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 221 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 221;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 221 order by id desc;\nselect * from stages where crm_configuration_id = 221 order by id desc;\n\nselect * from accounts where crm_configuration_id = 356 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 356 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 356 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 356 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 356;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 356 order by id desc;\nselect * from stages where crm_configuration_id = 356 order by id desc;\n\nselect * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)\nselect * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)\nselect * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4\nselect ce.* from calendars c\njoin users u on c.user_id = u.id\njoin calendar_events ce on c.id = ce.calendar_id\nwhere u.team_id = 260\nand (ce.start_time > '2025-02-21 00:00:00')\n;\n# calendar events 1207\n#\n\nselect * from opportunities where team_id = 260;\nSELECT * FROM crm_field_data WHERE object_id = 4696496;\n\nselect * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;\nselect * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')\n# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0\nand created_at > '2024-03-01 00:00:00'\norder by id desc; # 880 000, ringcentral, avaya\nSELECT * FROM participants WHERE activity_id = 26371744;\n\n# all activities 942 000 +\n# conference 7385 - scheduled 984 - external 343\n\nselect * from activities where id = 26321812;\nselect * from participants where activity_id = 26321812;\nselect * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);\nselect * from leads where id in (720428,689175,731546,645866,621037);\n\nselect * from users where id = 13841;\nselect * from opportunities where user_id = 9541;\nselect * from stages where id = 15900;\n\nselect * from accounts where\n# id IN (4160055,5053725,4965303,4896434)\nid in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)\n;\n\nselect * from activities where id = 26654935;\nSELECT * FROM opportunities WHERE id = 4803458;\n\nSELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;\nSELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time\nFROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);\n\nSELECT DISTINCT\n o.id, o.stage_id, s.name, a.title,\n a.*\nFROM activities a\n# INNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nINNER JOIN groups g ON u.group_id = g.id\nINNER JOIN opportunities o ON a.opportunity_id = o.id\nINNER JOIN stages s ON o.stage_id = s.id\nWHERE\n a.crm_configuration_id = 356\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 13841\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')\n AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')\n\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n )\n )\n AND (\n# s.id = 15900\n s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')\n OR s.uuid IS NULL -- Include records without opportunity stage\n )\n\nORDER BY a.actual_end_time DESC;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, willsc@leadforensics.com\nSELECT * FROM users WHERE team_id = 190;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 190\nand sa.provider = 'hubspot';\n\nselect * from role_user where user_id = 8474;\n\nselect * from crm_configurations where provider = 'bullhorn';\n\nSELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;\nSELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;\n\nSELECT * FROM opportunities WHERE id = 4732493;\nselect * from activities where opportunity_id = 4732493;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 443; # 358, 14315, andrea.romano@correrenaturale.com\nSELECT * FROM opportunities WHERE team_id = 443;\n\nSELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id\nFROM activities AS a\nJOIN stages AS s ON a.stage_id = s.id\nJOIN users AS u ON u.id = a.user_id\nJOIN teams AS t ON t.id = s.team_id\nWHERE u.team_id <> s.team_id and t.id > 135;\n\n\nSELECT\n crm_configuration_id,\n crm_provider_id,\n COUNT(*) as duplicate_count,\n GROUP_CONCAT(id) as stage_ids,\n GROUP_CONCAT(name) as stage_names\nFROM stages\nGROUP BY crm_configuration_id, crm_provider_id\nHAVING COUNT(*) > 1\nORDER BY duplicate_count DESC;\n\nselect * from stages where id IN (14898,14907);\n\nselect * from business_processes;\n\nSELECT *\nFROM crm_configurations\nWHERE team_id IN (\n SELECT team_id\n FROM crm_configurations\n GROUP BY team_id\n HAVING COUNT(*) > 1\n)\nORDER BY team_id;\n\nSELECT *\nFROM teams\nWHERE crm_id IN (\n SELECT crm_id\n FROM teams\n GROUP BY crm_id\n HAVING COUNT(*) > 1\n)\nORDER BY crm_id;\n\n# ***************************************************************************\nselect * from crm_configurations where provider = 'integration-app';\nSELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 andrea.romano@correrenaturale.com\nselect * from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect * from team_features where team_id = 358;\nselect * from activity_summary_logs;\n\nselect * from teams where id = 406;\n\n# ************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, srv.salesforce@sportfive.com\nselect * from activities where crm_configuration_id = 202 order by actual_end_time desc;\n\nSELECT * FROM users where id = 14637;\nSELECT * FROM teams where id = 267;\nSELECT * FROM groups where id = 1118;\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 202\n AND status IN ('completed', 'failed')\n AND recording_state != 'stopped'\n AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n AND (is_private = 0 OR user_id = 14637)\n AND (\n (\n actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n ) OR (\n actual_start_time IS NULL\n AND type IN ('sms-outbound', 'sms-inbound')\n AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND NOT EXISTS (\n SELECT 1\n FROM tracks\n WHERE\n tracks.activity_id = activities.id\n AND tracks.type IN ('audio', 'video')\n )\nORDER BY actual_end_time DESC;\n\nSELECT DISTINCT\n a.*\nFROM activities a\nINNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nWHERE\n a.crm_configuration_id = 202\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 14637\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND a.user_id = 14637\n )\n )\n\nORDER BY a.actual_end_time DESC\n;\n\nSELECT DISTINCT a.*\nFROM activities a\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams t ON u.team_id = t.id\n# INNER JOIN tracks tr ON a.id = tr.activity_id\n# INNER JOIN groups g ON u.group_id = g.id\nWHERE 1=1\n AND t.id = 267\n# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND tr.type NOT IN ('audio', 'video')\n AND (\n a.is_private = 0\n OR a.user_id = 14637\n )\n AND (\n (a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')\n OR (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'\n )\n )\n# and NOT EXISTS (\n# SELECT 1\n# FROM tracks t\n# WHERE t.activity_id = a.id\n# AND t.type IN ('audio', 'video')\n# )\n\nORDER BY a.actual_end_time DESC;\n\nSELECT * FROM tracks WHERE activity_id = 26485995;\n\nselect a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 202\n# and a.is_internal = 0\nand (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type IN (\"softphone\",\"softphone-inbound\",\"conference\",\"sms-inbound\")\nand a.status IN ('completed', 'failed')\n# and a.external_id is not null\norder by a.actual_end_time desc;\n\nselect * from activities a where a.crm_configuration_id = 202\nand a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'\n# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM teams WHERE name LIKE '%Tourlane%';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_field_data WHERE crm_field_id = 98809;\n\nselect * from users where status = 1 AND timezone = 'MDT';\n\nselect * from opportunities where id = 3769814;\nselect * from deal_risks where opportunity_id = 3769814;\n\nselect cp.* from crm_profiles cp\njoin users u on cp.user_id = u.id\njoin crm_configurations crm on cp.crm_configuration_id = crm.id\nwhere crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';\n\nselect * from crm_fields where id = 154575;\n\nselect * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';\nSELECT * FROM teams WHERE id = 176; # crm 148\nselect * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nselect * from crm_fields cf\njoin crm_configurations crm on crm.id = cf.crm_configuration_id\nwhere crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');\n\n# *********************************************************************************************\nSELECT * FROM users WHERE id IN (15415, 15418);\nSELECT * FROM groups WHERE id IN (1805,1806);\nSELECT * FROM playbooks WHERE id = 1860;\nSELECT * FROM playbook_categories WHERE id = 38634;\nSELECT * FROM crm_fields WHERE id = 189962;\n\nSELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 raza.gilani@vuelio.com\n\nSELECT * FROM crm_profiles WHERE user_id = 15415;\nSELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';\n\nselect * from sidekick_settings where team_id = 472;\n\nSELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418\nSELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415\n\n# *********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, salesforce-integrations@teamtailor.com\nselect * from crm_configurations where id = 218;\nSELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765\nSELECT * FROM users WHERE id IN (13232, 13230);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n0057R00000EPL5HQAX Inez Ekblad\n\n1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur\n\nSELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);\n\n############################################################################################\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id IN (94491,94493,94498);\nSELECT * FROM users WHERE id = 13658;\nSELECT * FROM teams WHERE id = 109;\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, katy.holden@strengthscope.comk\nSELECT * FROM stages WHERE crm_configuration_id = 390;\nselect * from business_processes where team_id = 481 and crm_configuration_id = 390;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 481\nand sa.provider = 'salesforce';\n\n\nSELECT * FROM users WHERE id = 15780; # team 462\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 462\nand sa.provider = 'hubspot';\n\n\nselect * from teams where id = 495;\nSELECT * FROM users WHERE id = 15794;\nselect * from social_accounts where sociable_id = 15794;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752\nSELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794\nSELECT * FROM activities WHERE crm_configuration_id = 407\nand status = 'completed' and type = 'conference'\norder by id desc;\n\nselect ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id\njoin permission_role pr on pr.role_id = ru.role_id\n join permissions p on p.id = pr.permission_id\nwhere team_id = 495 and p.name IN ('dial');\n\nselect * from permission_role;\n\nselect * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;\nSELECT * FROM activities WHERE id = 29512773;\nSELECT * FROM activities WHERE id IN (29042721,28991325,29002874);\n\nSELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 407\n# and a.id IN (29042721,28991325,29002874);\n\nSELECT * FROM users WHERE id = 15794;\nSELECT * FROM users WHERE team_id = 495;\nSELECT * FROM social_accounts WHERE sociable_id = 15794;\nSELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';\nSELECT * FROM contacts WHERE team_id = 495;\nSELECT * FROM leads WHERE team_id = 495;\nSELECT * FROM accounts WHERE team_id = 495;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 407;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 407;\nSELECT * FROM crm_configurations WHERE id = 407;\nSELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'\nand user_id IS NOT NULL and is_closed = 1 and is_won = 1;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103\nSELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064\nSELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');\n\n# *********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 325\nand sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085\nSELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733\nSELECT * FROM activity_summary_logs where activity_id = 28719733;\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444\nSELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';\nSELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630\nselect * from activities where crm_configuration_id = 356 and lead_id = 841732;\n\nSELECT * from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 356;\n\nselect * from activities where crm_configuration_id = 356\nand actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'\norder by id desc;\n\nselect * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;\nselect * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\n\nselect * from team_features where team_id = 260;\nselect * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;\n\nselect * from crm_fields;\nselect * from crm_layout_entities;\n\nSELECT * FROM teams WHERE name LIKE '%Optable%';\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id in (94491,94493,94498);\n\nselect * from teams where crm_id IS NULL;\n\nSELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;\n\n# *************************************************************************************************\nselect * from team_domains where team_id = 399;\nSELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207\n\nselect * from calendar_events where id = 5163781;\nSELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896\nSELECT * FROM participants WHERE activity_id = 29443896;\nselect * from contacts where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\nselect * from leads where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\n\nselect * from activities where user_id = 14937 order by created_at ;\n\nselect * from users where id = 14937;\n\nselect * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';\nselect * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';\n\nselect * from activities a join participants p on a.id = p.activity_id\nwhere crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';\n\n# *************************************************************************************************\nSELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';\nSELECT * FROM opportunities WHERE team_id = 379 order by id desc;\nSELECT * FROM teams WHERE id = 379;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379 and sociable_id = 13852\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE id = 307;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 307;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307\n and id IN (144750,144855,145158,155227);\n\nSELECT * FROM activities;\n\n\nselect * from activities\nwhere created_at > '2025-07-01 00:00:00'\n# and created_at < '2025-08-01 00:00:00'\nand type not in ('email-outbound', 'email-inbound')\nand account_id is null\nand contact_id is null\nand lead_id is null\nand opportunity_id is not null\n;\nSELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);\nSELECT * FROM crm_configurations WHERE id in (335,301,200);\n\nselect * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';\n\nSELECT * FROM teams WHERE name LIKE '%Resights%';\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nselect * from teams where id IN (442);\n\nselect * from activities\nwhere crm_configuration_id = 177\nand provider = 'amazon-connect'\n order by id desc;\n# and source <> 'gong';\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nSELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;\n\n\nselect * from crm_configurations where store_transcript = 1;\nSELECT * FROM teams WHERE id IN (80);\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 277\nand sa.provider = 'salesforce';\n\nselect * from activities where crm_configuration_id = 213 and account_id = 2511502;\n\nselect * from crm_configurations where id = 213;\n\nSELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604\nSELECT * FROM participants WHERE activity_id = 33981604;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 431\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223\nselect * from activity_summary_logs where activity_id = 33997223;\nselect * from activity_notes where activity_id = 33997223;\n\n# ***********************************\nSELECT * FROM teams WHERE name LIKE '%Abode%';\n\n\nselect * from features;\nselect * from teams t\nwhere t.status = 'active'\nand id NOT IN (select team_id from team_features where feature_id = 9)\n;\n\n\nselect * from playbook_layouts where playbook_id = 1725;\nSELECT * FROM activities WHERE uuid_to_bin('65cc283c-4849-49e6-927f-4c281c8fea19') = uuid; # 34297473\nselect * from teams where id = 318;\nselect * from crm_configurations where team_id = 318;\nselect * from playbooks where team_id = 318;\nSELECT * FROM crm_layouts where crm_configuration_id = 381;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1259;\nSELECT * FROM crm_fields WHERE id IN (192938,192936,192939);\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1266;\nSELECT * FROM crm_fields WHERE id IN (192980,192991,192997,192998,193064,193067);\n\nSELECT * FROM activities WHERE uuid_to_bin('a902289b-285c-48eb-9cc2-6ad6c5d938f5') = uuid; # 34297533\n\n\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nSELECT * FROM crm_fields WHERE id IN (131668,131669,131670,131671,131676,131797);\n\nSELECT * FROM teams WHERE name LIKE '%Peripass%'; # 351, 281, 12124\nselect * from crm_layouts where crm_configuration_id = 281;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nselect * from crm_fields where crm_configuration_id = 281 and id in (131668,131669,131670,131671,131676,131797);\nselect * from opportunities where crm_configuration_id = 281;\n\nSELECT * FROM activities WHERE id IN (34211315, 34130075);\nSELECT * FROM crm_field_data WHERE object_id IN (34211315, 34130075);\n\nselect cf.crm_configuration_id, cle.crm_layout_id, cle.id, cf.id from crm_field_data cfd\njoin crm_layout_entities cle on cle.id = cfd.crm_layout_entity_id\njoin crm_fields cf on cle.crm_field_id = cf.id\nwhere cf.deleted_at IS NOT NULL\nGROUP BY cle.id, cf.id;\n\nselect * from crm_layouts where id IN (355);\nselect u.email, t.crm_id, t.* from teams t\njoin users u on u.id = t.owner_id\nwhere crm_id IN (97);\n\nSELECT * FROM crm_fields WHERE id = 96492;\n\nselect * from permissions;\nselect * from permission_role where permission_id = 247;\nselect * from roles;\n\nselect * from migrations;\n# *****************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('291e3c21-11cc-4728-aee7-6e4bedf86d72') = uuid; # 34262174\nSELECT * FROM crm_configurations WHERE id = 301;\nSELECT * FROM teams WHERE id = 343;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from participants where activity_id = 34262174;\n\nselect * from contacts where crm_configuration_id = 301 and id = 6976326;\nselect * from accounts where crm_configuration_id = 301 and id IN (4647626, 4815829); # 30761335403\n\nselect * from activity_summary_logs where activity_id = 34262174;\n\nselect * from users where status = 1 AND timezone = 'EST';\n\n# ****************************************************************************\nSELECT * FROM users WHERE id = 13869;\nSELECT * FROM crm_configurations WHERE id = 320;\nSELECT * FROM teams WHERE id = 401;\n\nSELECT * FROM activities WHERE uuid_to_bin('2228c16f-10be-48d5-90d4-67385219dc01') = uuid; # 29670601\n\nSELECT * FROM accounts WHERE id = 7761483;\nSELECT * FROM opportunities WHERE id = 6051814;\n\nSELECT * FROM teams WHERE name LIKE '%Seedlegals%';\n\n;select * from opportunities where updated_at > '2025-10-11' AND crm_provider_id = '34713761166';\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 177;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 577;\nSELECT * FROM crm_fields WHERE id IN (68458,68459,68480,68497,68524,68530,68554,68618,68662,68781,68810,68898,68981,69049,97467);\n\nSELECT t.id, crm.id, t.name, crm.sync_objects, crm.provider, crm.last_synced_at FROM crm_configurations crm join teams t on t.crm_id = crm.id\nwhere t.status = 'active' AND crm.provider = 'hubspot' AND crm.last_synced_at < '2025-10-22 00:00:00';\n\nSELECT * FROM activities WHERE uuid_to_bin('fa09449f-cba9-496a-b8f3-865cd3c72351') = uuid;\nSELECT * FROM crm_configurations where id = 184;\nSELECT * FROM teams WHERE id = 246;\nSELECT * FROM social_accounts WHERE sociable_id = 9259 and provider = 'hubspot';\n\nSELECT * FROM users WHERE email LIKE '%rhian.old@bud.co.uk%'; # 17700\nSELECT * FROM teams WHERE id = 551;\n\nSELECT * FROM crm_configurations WHERE id = 471;\nSELECT * FROM activities WHERE crm_configuration_id = 471 and crm_provider_id IS NOT NULL;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 471;\nSELECT * FROM crm_fields WHERE id = 307260;\nSELECT * FROM crm_field_values WHERE crm_field_id = 307260;\n\nselect * from crm_layouts where crm_configuration_id = 471;\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1547;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1548;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 551 and sa.provider = 'hubspot';\n\nSELECT * FROM teams WHERE name LIKE '%$PCS%';\n\n# ********************************************************************************************************\nselect * from crm_configurations crm\njoin teams t on t.crm_id = crm.id\nwhere t.status = 'active'\nand crm.provider = 'hubspot';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from crm_configurations where id = 331; # 416\nSELECT * FROM teams WHERE id = 416;\nSELECT * FROM opportunities WHERE team_id = 190;\n\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%';\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 190 and sa.provider = 'hubspot';\n\n\n\nSELECT * FROM teams WHERE name LIKE '%Rapaport%'; # 431, 337\nSELECT * FROM teams where id = 431;\nSELECT * FROM crm_configurations where team_id = 431;\nSELECT * FROM activity_providers where team_id = 431;\nSELECT * FROM activities where crm_configuration_id = 337 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 431 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%BiP%'; # 401, 320\nSELECT * FROM teams where id = 401;\nSELECT * FROM crm_configurations where team_id = 401;\nSELECT * FROM activity_providers where team_id = 401;\nSELECT * FROM activities where crm_configuration_id = 320 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 401 and sa.provider = 'salesforce';\n\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 307; # 379 - Story Terrace Inc , portalId: 3921157\nSELECT * FROM contacts WHERE team_id = 379 and updated_at > '2026-01-31 11:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 379 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; # 563 - LATUS Group (ad94d501-5d09-44fd-878f-ca3a9f8865c3) , portalId: 3904501\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 338; # 432 - Formalize , portalId: 9214205\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 432 and sa.provider = 'hubspot';\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 436; # 519 - Moxso , portalId: 25531989\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 96; # 119 - Nourish Care , portalId: 26617984\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 331; # 416 - The National College , portalId: 7213852\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 308; # 380 - Foodles , portalId: 7723616\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 379; # 471 - imat-uve , portalId: 9177354\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 465; # 545 - Spotler , portalId: 144759271\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 455; # 537 - indevis , portalId: 25666868\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 200; # 265 - Jobadder , portalId: 6426676\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 335; # 429 - Eletive , portalId: 6110563\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 363; # 456 - Global Group , portalId: 8901981\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 297; # 369 - Unbiased , portalId: 9229005\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 353; # 449 - Fuuse , portalId: 25781745\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 487; # 566 - Nimbus , portalId: 39982590\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 487;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1630;\nselect * from crm_fields where crm_configuration_id = 487 and\n(uuid_to_bin('4c6b2971-64d4-45b8-b377-427be758b5a5') = uuid or uuid_to_bin('59e368d8-65a0-4b77-b611-db37c99fbe68') = uuid);\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 420; # 506 - voiio , portalId: 145629154\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 479; # 558 - Momice , portalId: 535962\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 59; # 80 - Storyclash GmbH , portalId: 4268479\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 175; # 203 - Team iAM , portalId: 5534732\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 368; # 460 - OneTouch Health , portalId: 5534732183355\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\n\n\nselect * from users where id = 29643;\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM teams WHERE name LIKE '%Buynomics%'; # 462, 482, 14910\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\n# and description like '%The call focused on understanding Welch%'\norder by id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 462 and sa.provider = 'salesforce';\n\nselect * from contacts where crm_configuration_id = 482 and name = 'Cyndall Hill'; # 15504749\nselect * from contacts where id = 10891096; # 482\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\nand contact_id = 15504749\norder by id desc;\n\nselect * from activities where id = 36793003; # 96cc7bc1-8622-4d27-92f4-baf664fc1a56, 00UOf00000PDdOXMA1\nselect * from transcription where id = 7646782;\nselect * from ai_prompts where transcription_id = 7646782;\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7a8471a3-847e-4822-802b-ddf426bbc252') = uuid; # 37370018\nSELECT * FROM activity_summary_logs WHERE activity_id = 37370018;\nSELECT * FROM teams WHERE id = 555;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 555 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7c17b8aa-09df-4f85-a0f7-51f47afd712d') = uuid; # 37395250\nSELECT * FROM activities WHERE uuid_to_bin('14d60388-260d-494b-aa0d-63fdb1c78026') = uuid; # 37395250\n\nSELECT a.* FROM activities a JOIN crm_configurations c on c.id = a.crm_configuration_id\nwhere a.type IN ('softphone', 'softphone-outbound') and c.provider = 'hubspot'\nand a.provider NOT IN ('hubspot')\n# and a.provider IN ('salesloft')\n# and c.id NOT IN (70)\n# and a.duration > 30\n# and actual_start_time > '2026-02-05 00:00:00'\norder by a.id desc;\n\nSELECT * FROM activities WHERE id = 37549787;\nSELECT * FROM crm_profiles WHERE user_id = 17613;\n\nSELECT * FROM crm_configurations WHERE id = 70;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 93 and sa.provider = 'hubspot';\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations WHERE id = 373; # KPSBremen.de 465 # - no social account\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 465 and sa.provider = 'hubspot';\n\nselect * from crm_configurations where id = 494;\n\nSELECT * FROM teams WHERE name LIKE '%splose%'; # 572, 495, 18708\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 572 and sa.provider = 'pipedrive';\n\nselect * from opportunities where team_id = 572\n# and name like '%Onebright%'\n# and is_closed = 1 and is_won = 0\n order by id desc;\n\n\nselect * from users where deleted_at is null and status = 2;\n\nselect * from contacts where id = 17900517;\nselect * from accounts where id = 10109838;\nselect * from opportunities where id = 6955880;\n\nselect * from opportunity_contacts where opportunity_id = 6955880;\nselect * from opportunity_contacts where contact_id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nSELECT * FROM activities WHERE uuid_to_bin('adcb8331-5988-4353-834e-383a355abba2') = uuid; # 38056424, crm 104659682404\nselect * from teams where id = 456;\nSELECT * FROM crm_configurations WHERE id = 363;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 456 and sa.provider = 'hubspot';\n\nselect * from crm_layouts where crm_configuration_id = 363;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (1203, 1204, 1635);\nSELECT * FROM crm_fields WHERE id IN (181536, 181538, 213455);\n\nSELECT * FROM teams WHERE name LIKE '%Electric%'; # 342, 272, 12767\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and name like 'NORTHUMBRIA POL%'; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 order by remotely_created_at asc; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and updated_at > '2026-01-01 00:00:00';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 272 and object_type = 'opportunity';\nSELECT * FROM crm_field_values WHERE crm_field_id = 127164;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\n\nSELECT * FROM teams WHERE id = 472;\nSELECT * FROM crm_configurations WHERE id = 380;\nselect * from activities where id = 38285673; # 38285673\nSELECT * FROM users WHERE id = 16942;\nSELECT * FROM groups WHERE id = 1964;\nSELECT * FROM playbooks WHERE id = 2033;\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 499; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1678;\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\n\nSELECT * FROM activities WHERE uuid_to_bin('96b1261f-2357-49f9-ab38-23ce12008ea0') = uuid;\n\nselect * from contacts c\nwhere c.crm_configuration_id = 370 order by c.updated_at desc;\n\nSELECT * FROM participants where activity_id = 38833541;\nSELECT * FROM participants where activity_id = 39216301;\nSELECT * FROM activity_summary_logs where activity_id = 39216301;\nSELECT * FROM activities WHERE uuid_to_bin('c7d99fbe-1fb1-41f2-8f4d-52e2bf70e1e9') = uuid; # 38833541, crm 478116564181\nSELECT * FROM activities WHERE uuid_to_bin('2e6ff4d3-9faa-447a-a8c1-9acde4d885ae') = uuid; # 39216301, crm 480171536586\nselect * from crm_profiles where crm_configuration_id = 319 and crm_provider_id = 525785080;\nselect * from opportunities where crm_configuration_id = 319 and crm_provider_id = 410150124747;\nselect * from accounts where crm_configuration_id = 319 and crm_provider_id = 47150650569;\nselect * from contacts where crm_configuration_id = 319 and crm_provider_id IN ('665587441856', '742723347700');\n# owner 13236 525785080\n# contact 1 16779180 665587441856 - activity - Alex Howes alex@supportroom.com created 2026-01-26\n# contact 2 19247563 742723347700 - ash@supportroom.com 2026-03-24\n# company 4176133 47150650569\n# deal 7100953 410150124747\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 400 and sa.provider = 'hubspot';\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556; # owner: 18101, crm: 477\nselect * from crm_configurations where id = 477;\nSELECT * FROM users WHERE id = 18101;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'integration-app';\n\nselect * from opportunities where id = 7594349;\nselect * from opportunity_stages where opportunity_id = 7594349 order by created_at desc;\nselect * from business_processes where id = 6024;\nselect * from business_process_stages where stage_id = 16352;\nselect * from business_process_stages where business_process_id = 6024;\nselect * from stages where team_id = 459;\nselect * from teams where id = 459;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 459 and sa.provider = 'hubspot';\n\nSELECT os.stage_id, s.crm_provider_id, s.name, COUNT(*) as cnt\nFROM opportunity_stages os\nJOIN stages s ON s.id = os.stage_id\nWHERE os.opportunity_id = 7594349\nGROUP BY os.stage_id, s.crm_provider_id, s.name\nORDER BY cnt DESC;\n\nSELECT s.id, s.crm_provider_id, s.name, s.team_id, s.crm_configuration_id\nFROM stages s\nJOIN business_process_stages bps ON bps.stage_id = s.id\nWHERE bps.business_process_id = 6024\nAND s.crm_provider_id = 'contractsent';\n\nselect * from stages where id IN (16352,20612,18281,7344,16378,16309,5036,15223,14535,6293,12098,11607)\n\nSELECT * FROM teams WHERE name LIKE '%Pulsar Group%'; # 472, 380, 15138, raza.gilani@vuelio.com\nselect * from playbooks where team_id = 472; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 2288;\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 380;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 472 and sa.provider = 'salesforce';","depth":4,"value":"SELECT * FROM team_features where team_id = 1;\n\nSELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922\nSELECT * FROM users WHERE team_id = 340; # 12015\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 340\nand sa.provider = 'salesforce';\n# and sa.provider = 'salesloft';\n\nselect * from crm_fields where crm_configuration_id = 270 and object_type = 'event';\n# 125558 - Event Type - Event_Type__c\n# 125552 - Event Status - Event_Status__c\n\nSELECT * FROM sidekick_settings WHERE team_id = 340;\n\nSELECT * FROM crm_field_values WHERE crm_field_id in (125552);\n\nselect * from activities where crm_configuration_id = 270\nand type = 'conference' and crm_provider_id IS NOT NULL\nand actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;\n\nSELECT * FROM activities WHERE id = 20871677;\nSELECT * FROM crm_field_data WHERE activity_id = 20871677;\n\nselect * from crm_layouts where crm_configuration_id = 270;\nselect * from crm_layout_entities where crm_layout_id in (886,887);\n\nSELECT * FROM crm_configurations WHERE id = 270;\n\nselect * from playbooks where team_id = 340; # 1514\nselect * from groups where team_id = 340;\nSELECT * FROM crm_fields WHERE id IN (125393, 125401);\n\nselect g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g\njoin playbooks p on g.playbook_id = p.id\njoin crm_fields f on p.activity_field_id = f.id\nwhere g.team_id = 340;\n\nSELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716\nselect * from crm_field_data where object_id = 20448716;\n\nselect * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008\nselect * from opportunities where team_id = 343;\nselect * from opportunities where team_id = 343 and crm_provider_id = '18099102526';\nselect * from opportunities where team_id = 343 and account_id = 945217482;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from accounts where team_id = 343 order by name asc;\n\nselect * from stages where crm_configuration_id = 273 and type = 'opportunity';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143\nSELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;\nSELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';\nSELECT * FROM activities WHERE id = 20717903;\n\nselect * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 353\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, l.atkinson@mwbsolutions.co.uk\nSELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;\n# id: 20940638, user: 12022, contact: 5305871\nSELECT * FROM activity_summary_logs WHERE activity_id = 20940638;\nselect * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 345\nand sa.provider = 'hubspot';\n\nselect * from users where team_id = 345 and id = 12022;\nSELECT * FROM crm_profiles WHERE user_id = 12022;\nSELECT * FROM participants WHERE activity_id = 20940638;\nSELECT * FROM users u\nJOIN crm_profiles cp ON u.id = cp.user_id\nWHERE u.team_id = 345;\n\nselect * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871\n\nselect * from team_features where team_id = 345;\nSELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197\nSELECT * FROM participants WHERE activity_id = 20897406;\n\n\n\nSELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912\nSELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';\n\n\nSELECT * FROM activities WHERE id = 20946641;\nSELECT * FROM crm_profiles WHERE user_id = 10211;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, triger@lunio.ai\nSELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';\nselect * from stages where crm_configuration_id = 97 and type = 'opportunity';\nselect * from opportunities where team_id = 120;\n\n\nselect * from crm_configurations crm join teams t on crm.id = t.crm_id\nwhere 1=1\nAND t.current_billing_plan IS NOT NULL\nAND crm.auto_sync_activity = 0\nand crm.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,james.lewendon@exclaimer.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 270\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956\nSELECT * FROM crm_profiles WHERE user_id = 11446;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, alex.chikly@cygnetise.com\nselect * from playbooks where team_id = 372;\nselect * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340\nSELECT * FROM crm_field_values WHERE crm_field_id = 141340;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 372\nand sa.provider = 'salesforce';\n\nselect * from crm_profiles where crm_configuration_id = 300;\nSELECT * FROM crm_configurations WHERE team_id = 372;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,mfa@planday.com\nSELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756\nselect * from crm_field_data where object_id = 3207756;\nSELECT * FROM crm_fields WHERE id = 111834;\n\nselect f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value\nFROM crm_fields f\nJOIN crm_field_data fd ON f.id = fd.crm_field_id\nWHERE f.crm_configuration_id = 242\nAND f.object_type = 'opportunity'\nAND fd.object_id IN (3207756)\nORDER BY fd.object_id, fd.updated_at;\n\nSELECT * FROM crm_configurations WHERE auto_connect = 1;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,salesforce-admin@tourlane.com\nselect * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id\nwhere g.team_id = 187;\n\nselect * from `groups` where team_id = 187;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 187\nand sa.provider = 'salesforce';\n\n# Destination - 98870 - Destination__c\n# Stage - 79014 - StageName\n# Land Arrangement - 98856 - Land_Arrangement__c\n# Flight - 98848 - Flight__c\n# Last activity date - 98812 - LastActivityDate\n# Last modified date - 98809 - LastModifiedDate\n# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c\n# next call - 98864 - Next_Call__c\n\nselect * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\nselect * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';\nselect * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;\nselect * from activities where opportunity_id = 3538248;\n\nSELECT * FROM crm_profiles WHERE user_id = 8150;\n\nselect * from deal_risks where opportunity_id = 3538248;\n\nselect * from teams where crm_id IS NULL;\n\nSELECT opp.id AS opportunity_id,\n u.group_id AS group_id,\n MAX(\n CASE\n WHEN a.type IN (\"sms-inbound\", \"sms-outbound\") THEN a.created_at\n ELSE a.actual_end_time\n END) as last_date\nFROM opportunities opp\nleft join activities a on a.opportunity_id = opp.id\ninner join users u on opp.user_id = u.id\nwhere opp.user_id IN (9951)\n\nAND opp.is_closed = 0\nand a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL\ngroup by opp.id;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,polly.morphew@cybsafe.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 301;\nSELECT * FROM contacts WHERE id = 6612363;\nSELECT * FROM accounts WHERE id = 4235676;\nSELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;\nselect * from opportunity_stages where opportunity_id = 4503759;\n# SELECT * FROM opportunities WHERE id = 4569937;\n\nselect * from activities where crm_configuration_id = 301;\nSELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370\nSELECT * FROM participants WHERE activity_id = 26330370;\n\nSELECT * FROM teams WHERE id = 375;\nselect * from playbooks where team_id = 375;\n\nselect * from stages where crm_configuration_id = 301 and type = 'opportunity';\n\nselect * from teams;\nselect * from contact_roles;\n\nSELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';\n\nselect * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;\n\nSELECT * FROM crm_field_data WHERE object_id = 3771706;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'\nand crm_provider_id LIKE \"%traffic_light%\";\nSELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);\n\nSELECT fd.* FROM opportunities o\nJOIN crm_field_data fd ON o.id = fd.object_id\nWHERE o.team_id = 343\n# and o.user_id IS NOT NULL\nand fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)\nand fd.value != ''\norder by value desc\n# group by o.id\n;\n\nSELECT * FROM opportunities WHERE id = 3769843;\n\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, salesforce-admin@tourlane.com\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,aswini.mishra@fundingcircle.com\nSELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839\n\n\nSELECT * FROM opportunities WHERE id = 3855992;\n\nSELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988\n\nSELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';\n\nselect * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507\nSELECT * FROM crm_field_data WHERE object_id = 5874411;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379\nand sa.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, nikhil.kumar@mention-me.com\nSELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, salesforce-admin@tourlane.com\nSELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793\nselect * from generic_ai_prompts where subject_id = 3537793;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, triger@lunio.ai\nSELECT * FROM crm_configurations WHERE id = 97;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 97;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;\nSELECT * FROM crm_fields WHERE id = 32682;\n\nselect cfd.value, o.* from opportunities o\njoin crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682\nwhere team_id = 120\nand cfd.value != ''\n;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 120\nand sa.provider = 'salesforce';\n\nselect * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';\nSELECT * FROM crm_field_data WHERE object_id = 2313439;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 410;\nSELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';\nselect * from scorecards where team_id = 410;\nselect * from scorecard_rules;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, aswini.mishra@fundingcircle.com\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\njoin users u on o.user_id = u.id\nwhere a.crm_configuration_id = 177 and a.type LIKE '%email-out%'\n# and a.actual_end_time > '2024-12-16 00:00:00'\n# and o.remotely_created_at > '2024-12-01 00:00:00'\n# and u.group_id = 1014\nand u.id = 9021\norder by a.id desc;\nSELECT * FROM opportunities WHERE id in (3981384,4017346);\nSELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);\n\nselect * from users where id = 9021;\nselect * from inboxes where user_id = 9021;\n\nselect * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';\n\nselect * from email_messages where team_id = 220\nand orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'\nand subject LIKE '%Personal%'\n# and 'from' = 'credit@fundingcircle.com'\n;\n\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\nwhere a.user_id = 9021 and a.type LIKE '%email-out%'\nand a.actual_end_time > '2024-12-18 00:00:00'\nand o.user_id IS NOT NULL\nand o.remotely_created_at > '2024-12-01 00:00:00'\norder by a.id desc;\n\nSELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;\nselect * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;\n\nselect * from team_settings where name IN ('useCloseDate');\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, jfarrell@hurree.co\nSELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 104\nand sa.provider = 'hubspot';\n\nselect * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'\nselect * from teams where crm_id IS NULL;\n\nselect t.name as 'team', u.name as 'owner', u.email, u.phone\nfrom teams t\njoin activity_providers ap on t.id = ap.team_id\njoin users u on t.owner_id = u.id\nwhere 1=1\n and t.status = 'active'\n and ap.is_enabled = 1\n# and u.status = 1\n and ap.provider = 'ms-teams';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nSELECT * FROM teams WHERE id = 442; # 14293\nselect * from users where team_id = 442;\nselect * from social_accounts sa where sa.sociable_id = 14293;\nselect * from invitations where team_id = 442;\n\n# ********************************************************************************************************\nSELECT * FROM users WHERE email LIKE '%nea.liikamaa@eletive.com%'; # 14022\nSELECT * FROM teams WHERE id = 429;\nselect * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);\nselect * from activities where opportunity_id in (4340436,4353519);\n\nselect * from transcription where activity_id IN (25630961,25381771);\nselect * from generic_ai_prompts where subject_id IN (4353519);\n\nSELECT\n a.id as activity_id,\n a.opportunity_id,\n a.type as activity_type,\n a.language,\n CONCAT(a.title, a.description) AS mail_content,\n e.from AS mail_from,\n e.to AS mail_to,\n e.subject AS mail_subject,\n e.body AS mail_body,\n p.type as prompt_type,\n p.status as prompt_status,\n p.content AS prompt_content,\n a.actual_start_time as created_at\nFROM activities a\n LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL\n LEFT JOIN email_messages e ON a.id = e.activity_id\nWHERE a.actual_start_time > '2024-01-01 00:00:00'\n AND a.opportunity_id IN (4353519)\n AND a.status IN ('completed', 'received', 'delivered')\n AND a.deleted_at IS NULL\n AND a.type NOT IN ('sms-inbound', 'sms-outbound')\nORDER BY a.opportunity_id ASC, a.id ASC;\n\nSELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293\nSELECT * FROM teams WHERE id = 442;\nSELECT * FROM crm_configurations WHERE id = 344;\nselect * from team_features where team_id = 442;\nselect * from groups where team_id = 442;\nselect * from playbooks where team_id = 442;\nselect * from playbook_categories where playbook_id = 1729;\nselect * from crm_fields where crm_configuration_id = 344 and id = 172024;\nSELECT * FROM crm_field_values WHERE crm_field_id = 172024;\nselect * from crm_layouts where crm_configuration_id = 344;\nselect * from playbook_layouts where playbook_id = 1729;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444\n\nselect s.*\n# , s.sent_at, u.name, a.*\nfrom activity_summary_logs s\ninner join activities a on a.id = s.activity_id\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 356\nand s.sent_at > date_sub(now(), interval 60 day)\norder by a.actual_end_time desc;\n\nselect * from activities a\n# inner join activity_summary_logs s on s.activity_id = a.id\nwhere a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)\n# and a.crm_provider_id is not null\n# and provider <> 'ringcentral'\nand status = 'completed'\norder by a.actual_end_time desc;\n\nselect * from teams order by id desc; # 17328, 32, 17830, integration-account@jiminny.com\nSELECT * FROM users;\nSELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active\nSELECT * FROM teams WHERE id = 260;\nselect * from team_settings where team_id = 260;\nselect * from crm_configurations where team_id = 260;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 356;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;\n\nselect * from accounts where crm_configuration_id = 221 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 221 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 221 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 221 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 221;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 221 order by id desc;\nselect * from stages where crm_configuration_id = 221 order by id desc;\n\nselect * from accounts where crm_configuration_id = 356 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 356 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 356 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 356 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 356;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 356 order by id desc;\nselect * from stages where crm_configuration_id = 356 order by id desc;\n\nselect * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)\nselect * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)\nselect * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4\nselect ce.* from calendars c\njoin users u on c.user_id = u.id\njoin calendar_events ce on c.id = ce.calendar_id\nwhere u.team_id = 260\nand (ce.start_time > '2025-02-21 00:00:00')\n;\n# calendar events 1207\n#\n\nselect * from opportunities where team_id = 260;\nSELECT * FROM crm_field_data WHERE object_id = 4696496;\n\nselect * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;\nselect * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')\n# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0\nand created_at > '2024-03-01 00:00:00'\norder by id desc; # 880 000, ringcentral, avaya\nSELECT * FROM participants WHERE activity_id = 26371744;\n\n# all activities 942 000 +\n# conference 7385 - scheduled 984 - external 343\n\nselect * from activities where id = 26321812;\nselect * from participants where activity_id = 26321812;\nselect * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);\nselect * from leads where id in (720428,689175,731546,645866,621037);\n\nselect * from users where id = 13841;\nselect * from opportunities where user_id = 9541;\nselect * from stages where id = 15900;\n\nselect * from accounts where\n# id IN (4160055,5053725,4965303,4896434)\nid in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)\n;\n\nselect * from activities where id = 26654935;\nSELECT * FROM opportunities WHERE id = 4803458;\n\nSELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;\nSELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time\nFROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);\n\nSELECT DISTINCT\n o.id, o.stage_id, s.name, a.title,\n a.*\nFROM activities a\n# INNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nINNER JOIN groups g ON u.group_id = g.id\nINNER JOIN opportunities o ON a.opportunity_id = o.id\nINNER JOIN stages s ON o.stage_id = s.id\nWHERE\n a.crm_configuration_id = 356\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 13841\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')\n AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')\n\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n )\n )\n AND (\n# s.id = 15900\n s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')\n OR s.uuid IS NULL -- Include records without opportunity stage\n )\n\nORDER BY a.actual_end_time DESC;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, willsc@leadforensics.com\nSELECT * FROM users WHERE team_id = 190;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 190\nand sa.provider = 'hubspot';\n\nselect * from role_user where user_id = 8474;\n\nselect * from crm_configurations where provider = 'bullhorn';\n\nSELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;\nSELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;\n\nSELECT * FROM opportunities WHERE id = 4732493;\nselect * from activities where opportunity_id = 4732493;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 443; # 358, 14315, andrea.romano@correrenaturale.com\nSELECT * FROM opportunities WHERE team_id = 443;\n\nSELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id\nFROM activities AS a\nJOIN stages AS s ON a.stage_id = s.id\nJOIN users AS u ON u.id = a.user_id\nJOIN teams AS t ON t.id = s.team_id\nWHERE u.team_id <> s.team_id and t.id > 135;\n\n\nSELECT\n crm_configuration_id,\n crm_provider_id,\n COUNT(*) as duplicate_count,\n GROUP_CONCAT(id) as stage_ids,\n GROUP_CONCAT(name) as stage_names\nFROM stages\nGROUP BY crm_configuration_id, crm_provider_id\nHAVING COUNT(*) > 1\nORDER BY duplicate_count DESC;\n\nselect * from stages where id IN (14898,14907);\n\nselect * from business_processes;\n\nSELECT *\nFROM crm_configurations\nWHERE team_id IN (\n SELECT team_id\n FROM crm_configurations\n GROUP BY team_id\n HAVING COUNT(*) > 1\n)\nORDER BY team_id;\n\nSELECT *\nFROM teams\nWHERE crm_id IN (\n SELECT crm_id\n FROM teams\n GROUP BY crm_id\n HAVING COUNT(*) > 1\n)\nORDER BY crm_id;\n\n# ***************************************************************************\nselect * from crm_configurations where provider = 'integration-app';\nSELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 andrea.romano@correrenaturale.com\nselect * from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect * from team_features where team_id = 358;\nselect * from activity_summary_logs;\n\nselect * from teams where id = 406;\n\n# ************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, srv.salesforce@sportfive.com\nselect * from activities where crm_configuration_id = 202 order by actual_end_time desc;\n\nSELECT * FROM users where id = 14637;\nSELECT * FROM teams where id = 267;\nSELECT * FROM groups where id = 1118;\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 202\n AND status IN ('completed', 'failed')\n AND recording_state != 'stopped'\n AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n AND (is_private = 0 OR user_id = 14637)\n AND (\n (\n actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n ) OR (\n actual_start_time IS NULL\n AND type IN ('sms-outbound', 'sms-inbound')\n AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND NOT EXISTS (\n SELECT 1\n FROM tracks\n WHERE\n tracks.activity_id = activities.id\n AND tracks.type IN ('audio', 'video')\n )\nORDER BY actual_end_time DESC;\n\nSELECT DISTINCT\n a.*\nFROM activities a\nINNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nWHERE\n a.crm_configuration_id = 202\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 14637\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND a.user_id = 14637\n )\n )\n\nORDER BY a.actual_end_time DESC\n;\n\nSELECT DISTINCT a.*\nFROM activities a\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams t ON u.team_id = t.id\n# INNER JOIN tracks tr ON a.id = tr.activity_id\n# INNER JOIN groups g ON u.group_id = g.id\nWHERE 1=1\n AND t.id = 267\n# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND tr.type NOT IN ('audio', 'video')\n AND (\n a.is_private = 0\n OR a.user_id = 14637\n )\n AND (\n (a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')\n OR (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'\n )\n )\n# and NOT EXISTS (\n# SELECT 1\n# FROM tracks t\n# WHERE t.activity_id = a.id\n# AND t.type IN ('audio', 'video')\n# )\n\nORDER BY a.actual_end_time DESC;\n\nSELECT * FROM tracks WHERE activity_id = 26485995;\n\nselect a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 202\n# and a.is_internal = 0\nand (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type IN (\"softphone\",\"softphone-inbound\",\"conference\",\"sms-inbound\")\nand a.status IN ('completed', 'failed')\n# and a.external_id is not null\norder by a.actual_end_time desc;\n\nselect * from activities a where a.crm_configuration_id = 202\nand a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'\n# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM teams WHERE name LIKE '%Tourlane%';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_field_data WHERE crm_field_id = 98809;\n\nselect * from users where status = 1 AND timezone = 'MDT';\n\nselect * from opportunities where id = 3769814;\nselect * from deal_risks where opportunity_id = 3769814;\n\nselect cp.* from crm_profiles cp\njoin users u on cp.user_id = u.id\njoin crm_configurations crm on cp.crm_configuration_id = crm.id\nwhere crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';\n\nselect * from crm_fields where id = 154575;\n\nselect * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';\nSELECT * FROM teams WHERE id = 176; # crm 148\nselect * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nselect * from crm_fields cf\njoin crm_configurations crm on crm.id = cf.crm_configuration_id\nwhere crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');\n\n# *********************************************************************************************\nSELECT * FROM users WHERE id IN (15415, 15418);\nSELECT * FROM groups WHERE id IN (1805,1806);\nSELECT * FROM playbooks WHERE id = 1860;\nSELECT * FROM playbook_categories WHERE id = 38634;\nSELECT * FROM crm_fields WHERE id = 189962;\n\nSELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 raza.gilani@vuelio.com\n\nSELECT * FROM crm_profiles WHERE user_id = 15415;\nSELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';\n\nselect * from sidekick_settings where team_id = 472;\n\nSELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418\nSELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415\n\n# *********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, salesforce-integrations@teamtailor.com\nselect * from crm_configurations where id = 218;\nSELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765\nSELECT * FROM users WHERE id IN (13232, 13230);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n0057R00000EPL5HQAX Inez Ekblad\n\n1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur\n\nSELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);\n\n############################################################################################\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id IN (94491,94493,94498);\nSELECT * FROM users WHERE id = 13658;\nSELECT * FROM teams WHERE id = 109;\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, katy.holden@strengthscope.comk\nSELECT * FROM stages WHERE crm_configuration_id = 390;\nselect * from business_processes where team_id = 481 and crm_configuration_id = 390;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 481\nand sa.provider = 'salesforce';\n\n\nSELECT * FROM users WHERE id = 15780; # team 462\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 462\nand sa.provider = 'hubspot';\n\n\nselect * from teams where id = 495;\nSELECT * FROM users WHERE id = 15794;\nselect * from social_accounts where sociable_id = 15794;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752\nSELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794\nSELECT * FROM activities WHERE crm_configuration_id = 407\nand status = 'completed' and type = 'conference'\norder by id desc;\n\nselect ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id\njoin permission_role pr on pr.role_id = ru.role_id\n join permissions p on p.id = pr.permission_id\nwhere team_id = 495 and p.name IN ('dial');\n\nselect * from permission_role;\n\nselect * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;\nSELECT * FROM activities WHERE id = 29512773;\nSELECT * FROM activities WHERE id IN (29042721,28991325,29002874);\n\nSELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 407\n# and a.id IN (29042721,28991325,29002874);\n\nSELECT * FROM users WHERE id = 15794;\nSELECT * FROM users WHERE team_id = 495;\nSELECT * FROM social_accounts WHERE sociable_id = 15794;\nSELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';\nSELECT * FROM contacts WHERE team_id = 495;\nSELECT * FROM leads WHERE team_id = 495;\nSELECT * FROM accounts WHERE team_id = 495;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 407;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 407;\nSELECT * FROM crm_configurations WHERE id = 407;\nSELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'\nand user_id IS NOT NULL and is_closed = 1 and is_won = 1;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103\nSELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064\nSELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');\n\n# *********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 325\nand sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085\nSELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733\nSELECT * FROM activity_summary_logs where activity_id = 28719733;\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444\nSELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';\nSELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630\nselect * from activities where crm_configuration_id = 356 and lead_id = 841732;\n\nSELECT * from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 356;\n\nselect * from activities where crm_configuration_id = 356\nand actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'\norder by id desc;\n\nselect * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;\nselect * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\n\nselect * from team_features where team_id = 260;\nselect * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;\n\nselect * from crm_fields;\nselect * from crm_layout_entities;\n\nSELECT * FROM teams WHERE name LIKE '%Optable%';\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id in (94491,94493,94498);\n\nselect * from teams where crm_id IS NULL;\n\nSELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;\n\n# *************************************************************************************************\nselect * from team_domains where team_id = 399;\nSELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207\n\nselect * from calendar_events where id = 5163781;\nSELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896\nSELECT * FROM participants WHERE activity_id = 29443896;\nselect * from contacts where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\nselect * from leads where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\n\nselect * from activities where user_id = 14937 order by created_at ;\n\nselect * from users where id = 14937;\n\nselect * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';\nselect * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';\n\nselect * from activities a join participants p on a.id = p.activity_id\nwhere crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';\n\n# *************************************************************************************************\nSELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';\nSELECT * FROM opportunities WHERE team_id = 379 order by id desc;\nSELECT * FROM teams WHERE id = 379;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379 and sociable_id = 13852\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE id = 307;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 307;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307\n and id IN (144750,144855,145158,155227);\n\nSELECT * FROM activities;\n\n\nselect * from activities\nwhere created_at > '2025-07-01 00:00:00'\n# and created_at < '2025-08-01 00:00:00'\nand type not in ('email-outbound', 'email-inbound')\nand account_id is null\nand contact_id is null\nand lead_id is null\nand opportunity_id is not null\n;\nSELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);\nSELECT * FROM crm_configurations WHERE id in (335,301,200);\n\nselect * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';\n\nSELECT * FROM teams WHERE name LIKE '%Resights%';\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nselect * from teams where id IN (442);\n\nselect * from activities\nwhere crm_configuration_id = 177\nand provider = 'amazon-connect'\n order by id desc;\n# and source <> 'gong';\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nSELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;\n\n\nselect * from crm_configurations where store_transcript = 1;\nSELECT * FROM teams WHERE id IN (80);\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 277\nand sa.provider = 'salesforce';\n\nselect * from activities where crm_configuration_id = 213 and account_id = 2511502;\n\nselect * from crm_configurations where id = 213;\n\nSELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604\nSELECT * FROM participants WHERE activity_id = 33981604;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 431\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223\nselect * from activity_summary_logs where activity_id = 33997223;\nselect * from activity_notes where activity_id = 33997223;\n\n# ***********************************\nSELECT * FROM teams WHERE name LIKE '%Abode%';\n\n\nselect * from features;\nselect * from teams t\nwhere t.status = 'active'\nand id NOT IN (select team_id from team_features where feature_id = 9)\n;\n\n\nselect * from playbook_layouts where playbook_id = 1725;\nSELECT * FROM activities WHERE uuid_to_bin('65cc283c-4849-49e6-927f-4c281c8fea19') = uuid; # 34297473\nselect * from teams where id = 318;\nselect * from crm_configurations where team_id = 318;\nselect * from playbooks where team_id = 318;\nSELECT * FROM crm_layouts where crm_configuration_id = 381;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1259;\nSELECT * FROM crm_fields WHERE id IN (192938,192936,192939);\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1266;\nSELECT * FROM crm_fields WHERE id IN (192980,192991,192997,192998,193064,193067);\n\nSELECT * FROM activities WHERE uuid_to_bin('a902289b-285c-48eb-9cc2-6ad6c5d938f5') = uuid; # 34297533\n\n\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nSELECT * FROM crm_fields WHERE id IN (131668,131669,131670,131671,131676,131797);\n\nSELECT * FROM teams WHERE name LIKE '%Peripass%'; # 351, 281, 12124\nselect * from crm_layouts where crm_configuration_id = 281;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nselect * from crm_fields where crm_configuration_id = 281 and id in (131668,131669,131670,131671,131676,131797);\nselect * from opportunities where crm_configuration_id = 281;\n\nSELECT * FROM activities WHERE id IN (34211315, 34130075);\nSELECT * FROM crm_field_data WHERE object_id IN (34211315, 34130075);\n\nselect cf.crm_configuration_id, cle.crm_layout_id, cle.id, cf.id from crm_field_data cfd\njoin crm_layout_entities cle on cle.id = cfd.crm_layout_entity_id\njoin crm_fields cf on cle.crm_field_id = cf.id\nwhere cf.deleted_at IS NOT NULL\nGROUP BY cle.id, cf.id;\n\nselect * from crm_layouts where id IN (355);\nselect u.email, t.crm_id, t.* from teams t\njoin users u on u.id = t.owner_id\nwhere crm_id IN (97);\n\nSELECT * FROM crm_fields WHERE id = 96492;\n\nselect * from permissions;\nselect * from permission_role where permission_id = 247;\nselect * from roles;\n\nselect * from migrations;\n# *****************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('291e3c21-11cc-4728-aee7-6e4bedf86d72') = uuid; # 34262174\nSELECT * FROM crm_configurations WHERE id = 301;\nSELECT * FROM teams WHERE id = 343;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from participants where activity_id = 34262174;\n\nselect * from contacts where crm_configuration_id = 301 and id = 6976326;\nselect * from accounts where crm_configuration_id = 301 and id IN (4647626, 4815829); # 30761335403\n\nselect * from activity_summary_logs where activity_id = 34262174;\n\nselect * from users where status = 1 AND timezone = 'EST';\n\n# ****************************************************************************\nSELECT * FROM users WHERE id = 13869;\nSELECT * FROM crm_configurations WHERE id = 320;\nSELECT * FROM teams WHERE id = 401;\n\nSELECT * FROM activities WHERE uuid_to_bin('2228c16f-10be-48d5-90d4-67385219dc01') = uuid; # 29670601\n\nSELECT * FROM accounts WHERE id = 7761483;\nSELECT * FROM opportunities WHERE id = 6051814;\n\nSELECT * FROM teams WHERE name LIKE '%Seedlegals%';\n\n;select * from opportunities where updated_at > '2025-10-11' AND crm_provider_id = '34713761166';\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 177;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 577;\nSELECT * FROM crm_fields WHERE id IN (68458,68459,68480,68497,68524,68530,68554,68618,68662,68781,68810,68898,68981,69049,97467);\n\nSELECT t.id, crm.id, t.name, crm.sync_objects, crm.provider, crm.last_synced_at FROM crm_configurations crm join teams t on t.crm_id = crm.id\nwhere t.status = 'active' AND crm.provider = 'hubspot' AND crm.last_synced_at < '2025-10-22 00:00:00';\n\nSELECT * FROM activities WHERE uuid_to_bin('fa09449f-cba9-496a-b8f3-865cd3c72351') = uuid;\nSELECT * FROM crm_configurations where id = 184;\nSELECT * FROM teams WHERE id = 246;\nSELECT * FROM social_accounts WHERE sociable_id = 9259 and provider = 'hubspot';\n\nSELECT * FROM users WHERE email LIKE '%rhian.old@bud.co.uk%'; # 17700\nSELECT * FROM teams WHERE id = 551;\n\nSELECT * FROM crm_configurations WHERE id = 471;\nSELECT * FROM activities WHERE crm_configuration_id = 471 and crm_provider_id IS NOT NULL;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 471;\nSELECT * FROM crm_fields WHERE id = 307260;\nSELECT * FROM crm_field_values WHERE crm_field_id = 307260;\n\nselect * from crm_layouts where crm_configuration_id = 471;\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1547;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1548;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 551 and sa.provider = 'hubspot';\n\nSELECT * FROM teams WHERE name LIKE '%$PCS%';\n\n# ********************************************************************************************************\nselect * from crm_configurations crm\njoin teams t on t.crm_id = crm.id\nwhere t.status = 'active'\nand crm.provider = 'hubspot';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from crm_configurations where id = 331; # 416\nSELECT * FROM teams WHERE id = 416;\nSELECT * FROM opportunities WHERE team_id = 190;\n\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%';\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 190 and sa.provider = 'hubspot';\n\n\n\nSELECT * FROM teams WHERE name LIKE '%Rapaport%'; # 431, 337\nSELECT * FROM teams where id = 431;\nSELECT * FROM crm_configurations where team_id = 431;\nSELECT * FROM activity_providers where team_id = 431;\nSELECT * FROM activities where crm_configuration_id = 337 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 431 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%BiP%'; # 401, 320\nSELECT * FROM teams where id = 401;\nSELECT * FROM crm_configurations where team_id = 401;\nSELECT * FROM activity_providers where team_id = 401;\nSELECT * FROM activities where crm_configuration_id = 320 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 401 and sa.provider = 'salesforce';\n\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 307; # 379 - Story Terrace Inc , portalId: 3921157\nSELECT * FROM contacts WHERE team_id = 379 and updated_at > '2026-01-31 11:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 379 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; # 563 - LATUS Group (ad94d501-5d09-44fd-878f-ca3a9f8865c3) , portalId: 3904501\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 338; # 432 - Formalize , portalId: 9214205\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 432 and sa.provider = 'hubspot';\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 436; # 519 - Moxso , portalId: 25531989\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 96; # 119 - Nourish Care , portalId: 26617984\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 331; # 416 - The National College , portalId: 7213852\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 308; # 380 - Foodles , portalId: 7723616\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 379; # 471 - imat-uve , portalId: 9177354\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 465; # 545 - Spotler , portalId: 144759271\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 455; # 537 - indevis , portalId: 25666868\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 200; # 265 - Jobadder , portalId: 6426676\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 335; # 429 - Eletive , portalId: 6110563\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 363; # 456 - Global Group , portalId: 8901981\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 297; # 369 - Unbiased , portalId: 9229005\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 353; # 449 - Fuuse , portalId: 25781745\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 487; # 566 - Nimbus , portalId: 39982590\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 487;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1630;\nselect * from crm_fields where crm_configuration_id = 487 and\n(uuid_to_bin('4c6b2971-64d4-45b8-b377-427be758b5a5') = uuid or uuid_to_bin('59e368d8-65a0-4b77-b611-db37c99fbe68') = uuid);\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 420; # 506 - voiio , portalId: 145629154\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 479; # 558 - Momice , portalId: 535962\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 59; # 80 - Storyclash GmbH , portalId: 4268479\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 175; # 203 - Team iAM , portalId: 5534732\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 368; # 460 - OneTouch Health , portalId: 5534732183355\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\n\n\nselect * from users where id = 29643;\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM teams WHERE name LIKE '%Buynomics%'; # 462, 482, 14910\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\n# and description like '%The call focused on understanding Welch%'\norder by id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 462 and sa.provider = 'salesforce';\n\nselect * from contacts where crm_configuration_id = 482 and name = 'Cyndall Hill'; # 15504749\nselect * from contacts where id = 10891096; # 482\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\nand contact_id = 15504749\norder by id desc;\n\nselect * from activities where id = 36793003; # 96cc7bc1-8622-4d27-92f4-baf664fc1a56, 00UOf00000PDdOXMA1\nselect * from transcription where id = 7646782;\nselect * from ai_prompts where transcription_id = 7646782;\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7a8471a3-847e-4822-802b-ddf426bbc252') = uuid; # 37370018\nSELECT * FROM activity_summary_logs WHERE activity_id = 37370018;\nSELECT * FROM teams WHERE id = 555;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 555 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7c17b8aa-09df-4f85-a0f7-51f47afd712d') = uuid; # 37395250\nSELECT * FROM activities WHERE uuid_to_bin('14d60388-260d-494b-aa0d-63fdb1c78026') = uuid; # 37395250\n\nSELECT a.* FROM activities a JOIN crm_configurations c on c.id = a.crm_configuration_id\nwhere a.type IN ('softphone', 'softphone-outbound') and c.provider = 'hubspot'\nand a.provider NOT IN ('hubspot')\n# and a.provider IN ('salesloft')\n# and c.id NOT IN (70)\n# and a.duration > 30\n# and actual_start_time > '2026-02-05 00:00:00'\norder by a.id desc;\n\nSELECT * FROM activities WHERE id = 37549787;\nSELECT * FROM crm_profiles WHERE user_id = 17613;\n\nSELECT * FROM crm_configurations WHERE id = 70;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 93 and sa.provider = 'hubspot';\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations WHERE id = 373; # KPSBremen.de 465 # - no social account\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 465 and sa.provider = 'hubspot';\n\nselect * from crm_configurations where id = 494;\n\nSELECT * FROM teams WHERE name LIKE '%splose%'; # 572, 495, 18708\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 572 and sa.provider = 'pipedrive';\n\nselect * from opportunities where team_id = 572\n# and name like '%Onebright%'\n# and is_closed = 1 and is_won = 0\n order by id desc;\n\n\nselect * from users where deleted_at is null and status = 2;\n\nselect * from contacts where id = 17900517;\nselect * from accounts where id = 10109838;\nselect * from opportunities where id = 6955880;\n\nselect * from opportunity_contacts where opportunity_id = 6955880;\nselect * from opportunity_contacts where contact_id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nSELECT * FROM activities WHERE uuid_to_bin('adcb8331-5988-4353-834e-383a355abba2') = uuid; # 38056424, crm 104659682404\nselect * from teams where id = 456;\nSELECT * FROM crm_configurations WHERE id = 363;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 456 and sa.provider = 'hubspot';\n\nselect * from crm_layouts where crm_configuration_id = 363;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (1203, 1204, 1635);\nSELECT * FROM crm_fields WHERE id IN (181536, 181538, 213455);\n\nSELECT * FROM teams WHERE name LIKE '%Electric%'; # 342, 272, 12767\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and name like 'NORTHUMBRIA POL%'; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 order by remotely_created_at asc; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and updated_at > '2026-01-01 00:00:00';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 272 and object_type = 'opportunity';\nSELECT * FROM crm_field_values WHERE crm_field_id = 127164;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\n\nSELECT * FROM teams WHERE id = 472;\nSELECT * FROM crm_configurations WHERE id = 380;\nselect * from activities where id = 38285673; # 38285673\nSELECT * FROM users WHERE id = 16942;\nSELECT * FROM groups WHERE id = 1964;\nSELECT * FROM playbooks WHERE id = 2033;\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 499; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1678;\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\n\nSELECT * FROM activities WHERE uuid_to_bin('96b1261f-2357-49f9-ab38-23ce12008ea0') = uuid;\n\nselect * from contacts c\nwhere c.crm_configuration_id = 370 order by c.updated_at desc;\n\nSELECT * FROM participants where activity_id = 38833541;\nSELECT * FROM participants where activity_id = 39216301;\nSELECT * FROM activity_summary_logs where activity_id = 39216301;\nSELECT * FROM activities WHERE uuid_to_bin('c7d99fbe-1fb1-41f2-8f4d-52e2bf70e1e9') = uuid; # 38833541, crm 478116564181\nSELECT * FROM activities WHERE uuid_to_bin('2e6ff4d3-9faa-447a-a8c1-9acde4d885ae') = uuid; # 39216301, crm 480171536586\nselect * from crm_profiles where crm_configuration_id = 319 and crm_provider_id = 525785080;\nselect * from opportunities where crm_configuration_id = 319 and crm_provider_id = 410150124747;\nselect * from accounts where crm_configuration_id = 319 and crm_provider_id = 47150650569;\nselect * from contacts where crm_configuration_id = 319 and crm_provider_id IN ('665587441856', '742723347700');\n# owner 13236 525785080\n# contact 1 16779180 665587441856 - activity - Alex Howes alex@supportroom.com created 2026-01-26\n# contact 2 19247563 742723347700 - ash@supportroom.com 2026-03-24\n# company 4176133 47150650569\n# deal 7100953 410150124747\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 400 and sa.provider = 'hubspot';\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556; # owner: 18101, crm: 477\nselect * from crm_configurations where id = 477;\nSELECT * FROM users WHERE id = 18101;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'integration-app';\n\nselect * from opportunities where id = 7594349;\nselect * from opportunity_stages where opportunity_id = 7594349 order by created_at desc;\nselect * from business_processes where id = 6024;\nselect * from business_process_stages where stage_id = 16352;\nselect * from business_process_stages where business_process_id = 6024;\nselect * from stages where team_id = 459;\nselect * from teams where id = 459;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 459 and sa.provider = 'hubspot';\n\nSELECT os.stage_id, s.crm_provider_id, s.name, COUNT(*) as cnt\nFROM opportunity_stages os\nJOIN stages s ON s.id = os.stage_id\nWHERE os.opportunity_id = 7594349\nGROUP BY os.stage_id, s.crm_provider_id, s.name\nORDER BY cnt DESC;\n\nSELECT s.id, s.crm_provider_id, s.name, s.team_id, s.crm_configuration_id\nFROM stages s\nJOIN business_process_stages bps ON bps.stage_id = s.id\nWHERE bps.business_process_id = 6024\nAND s.crm_provider_id = 'contractsent';\n\nselect * from stages where id IN (16352,20612,18281,7344,16378,16309,5036,15223,14535,6293,12098,11607)\n\nSELECT * FROM teams WHERE name LIKE '%Pulsar Group%'; # 472, 380, 15138, raza.gilani@vuelio.com\nselect * from playbooks where team_id = 472; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 2288;\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 380;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 472 and sa.provider = 'salesforce';","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7558096554454067353
|
2146738311620114029
|
click
|
accessibility
|
NULL
|
Tests passed: 7
text/html
text/html
text/html
Proj Tests passed: 7
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
class TrackAutomatedReportGeneratedEvent implements ShouldQueue
{
use InteractsWithQueue;
private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';
private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';
public string $queue = Constants::QUEUE_DELAYABLE;
public function __construct(
private readonly UserPilotClient $userPilotClient,
private readonly AutomatedReportsService $automatedReportsService,
) {
}
public function handle(AutomatedReportGenerated $event): void
{
if (config('services.userpilot.token') === null) {
return;
}
$automatedReport = $event->automatedReport;
$payload = $this->buildPayload($automatedReport);
$eventName = $this->resolveEventName($automatedReport);
try {
foreach ($this->resolveUsers($automatedReport) as $user) {
$this->userPilotClient->track($user, $eventName, $payload);
}
} catch (GuzzleException $e) {
$this->release(3600);
}
}
/**
* @return array<UserContract>
*/
private function resolveUsers(AutomatedReport $automatedReport): array
{
if ($automatedReport->isAskJiminnyReport()) {
$creator = $automatedReport->getCreator();
return $creator !== null ? [$creator] : [];
}
return $this->automatedReportsService->getRecipientUserObjects($automatedReport);
}
private function buildPayload(AutomatedReport $automatedReport): array
{
return [
'report_type' => $automatedReport->getType(),
'frequency' => $automatedReport->getFrequency(),
];
}
private function resolveEventName(AutomatedReport $automatedReport): string
{
if ($automatedReport->isAskJiminnyReport()) {
return self::EVENT_NAME_ASK_JIMINNY_REPORT;
}
return self::EVENT_NAME_AUTOMATED_REPORT;
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
27
9
23
3
105
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM team_features where team_id = 1;
SELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922
SELECT * FROM users WHERE team_id = 340; # 12015
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 340
and sa.provider = 'salesforce';
# and sa.provider = 'salesloft';
select * from crm_fields where crm_configuration_id = 270 and object_type = 'event';
# 125558 - Event Type - Event_Type__c
# 125552 - Event Status - Event_Status__c
SELECT * FROM sidekick_settings WHERE team_id = 340;
SELECT * FROM crm_field_values WHERE crm_field_id in (125552);
select * from activities where crm_configuration_id = 270
and type = 'conference' and crm_provider_id IS NOT NULL
and actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;
SELECT * FROM activities WHERE id = 20871677;
SELECT * FROM crm_field_data WHERE activity_id = 20871677;
select * from crm_layouts where crm_configuration_id = 270;
select * from crm_layout_entities where crm_layout_id in (886,887);
SELECT * FROM crm_configurations WHERE id = 270;
select * from playbooks where team_id = 340; # 1514
select * from groups where team_id = 340;
SELECT * FROM crm_fields WHERE id IN (125393, 125401);
select g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g
join playbooks p on g.playbook_id = p.id
join crm_fields f on p.activity_field_id = f.id
where g.team_id = 340;
SELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716
select * from crm_field_data where object_id = 20448716;
select * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008
select * from opportunities where team_id = 343;
select * from opportunities where team_id = 343 and crm_provider_id = '18099102526';
select * from opportunities where team_id = 343 and account_id = 945217482;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
select * from accounts where team_id = 343 order by name asc;
select * from stages where crm_configuration_id = 273 and type = 'opportunity';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143
SELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;
SELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';
SELECT * FROM activities WHERE id = 20717903;
select * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 353
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;
# id: 20940638, user: 12022, contact: 5305871
SELECT * FROM activity_summary_logs WHERE activity_id = 20940638;
select * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 345
and sa.provider = 'hubspot';
select * from users where team_id = 345 and id = 12022;
SELECT * FROM crm_profiles WHERE user_id = 12022;
SELECT * FROM participants WHERE activity_id = 20940638;
SELECT * FROM users u
JOIN crm_profiles cp ON u.id = cp.user_id
WHERE u.team_id = 345;
select * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871
select * from team_features where team_id = 345;
SELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197
SELECT * FROM participants WHERE activity_id = 20897406;
SELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912
SELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';
SELECT * FROM activities WHERE id = 20946641;
SELECT * FROM crm_profiles WHERE user_id = 10211;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, [EMAIL]
SELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';
select * from stages where crm_configuration_id = 97 and type = 'opportunity';
select * from opportunities where team_id = 120;
select * from crm_configurations crm join teams t on crm.id = t.crm_id
where 1=1
AND t.current_billing_plan IS NOT NULL
AND crm.auto_sync_activity = 0
and crm.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 270
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956
SELECT * FROM crm_profiles WHERE user_id = 11446;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, [EMAIL]
select * from playbooks where team_id = 372;
select * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340
SELECT * FROM crm_field_values WHERE crm_field_id = 141340;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 372
and sa.provider = 'salesforce';
select * from crm_profiles where crm_configuration_id = 300;
SELECT * FROM crm_configurations WHERE team_id = 372;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,[EMAIL]
SELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756
select * from crm_field_data where object_id = 3207756;
SELECT * FROM crm_fields WHERE id = 111834;
select f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value
FROM crm_fields f
JOIN crm_field_data fd ON f.id = fd.crm_field_id
WHERE f.crm_configuration_id = 242
AND f.object_type = 'opportunity'
AND fd.object_id IN (3207756)
ORDER BY fd.object_id, fd.updated_at;
SELECT * FROM crm_configurations WHERE auto_connect = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,[EMAIL]
select * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id
where g.team_id = 187;
select * from `groups` where team_id = 187;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 187
and sa.provider = 'salesforce';
# Destination - 98870 - Destination__c
# Stage - 79014 - StageName
# Land Arrangement - 98856 - Land_Arrangement__c
# Flight - 98848 - Flight__c
# Last activity date - 98812 - LastActivityDate
# Last modified date - 98809 - LastModifiedDate
# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c
# next call - 98864 - Next_Call__c
select * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
select * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';
select * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;
select * from activities where opportunity_id = 3538248;
SELECT * FROM crm_profiles WHERE user_id = 8150;
select * from deal_risks where opportunity_id = 3538248;
select * from teams where crm_id IS NULL;
SELECT opp.id AS opportunity_id,
u.group_id AS group_id,
MAX(
CASE
WHEN a.type IN ("sms-inbound", "sms-outbound") THEN a.created_at
ELSE a.actual_end_time
END) as last_date
FROM opportunities opp
left join activities a on a.opportunity_id = opp.id
inner join users u on opp.user_id = u.id
where opp.user_id IN (9951)
AND opp.is_closed = 0
and a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL
group by opp.id;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_profiles WHERE crm_configuration_id = 301;
SELECT * FROM contacts WHERE id = 6612363;
SELECT * FROM accounts WHERE id = 4235676;
SELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;
select * from opportunity_stages where opportunity_id = 4503759;
# SELECT * FROM opportunities WHERE id = 4569937;
select * from activities where crm_configuration_id = 301;
SELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370
SELECT * FROM participants WHERE activity_id = 26330370;
SELECT * FROM teams WHERE id = 375;
select * from playbooks where team_id = 375;
select * from stages where crm_configuration_id = 301 and type = 'opportunity';
select * from teams;
select * from contact_roles;
SELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';
select * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;
SELECT * FROM crm_field_data WHERE object_id = 3771706;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'
and crm_provider_id LIKE "%traffic_light%";
SELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);
SELECT fd.* FROM opportunities o
JOIN crm_field_data fd ON o.id = fd.object_id
WHERE o.team_id = 343
# and o.user_id IS NOT NULL
and fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)
and fd.value != ''
order by value desc
# group by o.id
;
SELECT * FROM opportunities WHERE id = 3769843;
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, [EMAIL]
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,[EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839
SELECT * FROM opportunities WHERE id = 3855992;
SELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988
SELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894
SELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';
select * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507
SELECT * FROM crm_field_data WHERE object_id = 5874411;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379
and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793
select * from generic_ai_prompts where subject_id = 3537793;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, [EMAIL]
SELECT * FROM crm_configurations WHERE id = 97;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 97;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;
SELECT * FROM crm_fields WHERE id = 32682;
select cfd.value, o.* from opportunities o
join crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682
where team_id = 120
and cfd.value != ''
;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 120
and sa.provider = 'salesforce';
select * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';
SELECT * FROM crm_field_data WHERE object_id = 2313439;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 410;
SELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';
select * from scorecards where team_id = 410;
select * from scorecard_rules;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, [EMAIL]
select * from activities a
join opportunities o on a.opportunity_id = o.id
join users u on o.user_id = u.id
where a.crm_configuration_id = 177 and a.type LIKE '%email-out%'
# and a.actual_end_time > '2024-12-16 00:00:00'
# and o.remotely_created_at > '2024-12-01 00:00:00'
# and u.group_id = 1014
and u.id = 9021
order by a.id desc;
SELECT * FROM opportunities WHERE id in (3981384,4017346);
SELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);
select * from users where id = 9021;
select * from inboxes where user_id = 9021;
select * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';
select * from email_messages where team_id = 220
and orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'
and subject LIKE '%Personal%'
# and 'from' = '[EMAIL]'
;
select * from activities a
join opportunities o on a.opportunity_id = o.id
where a.user_id = 9021 and a.type LIKE '%email-out%'
and a.actual_end_time > '2024-12-18 00:00:00'
and o.user_id IS NOT NULL
and o.remotely_created_at > '2024-12-01 00:00:00'
order by a.id desc;
SELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;
select * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;
select * from team_settings where name IN ('useCloseDate');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 104
and sa.provider = 'hubspot';
select * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'
select * from teams where crm_id IS NULL;
select t.name as 'team', u.name as 'owner', u.email, u.phone
from teams t
join activity_providers ap on t.id = ap.team_id
join users u on t.owner_id = u.id
where 1=1
and t.status = 'active'
and ap.is_enabled = 1
# and u.status = 1
and ap.provider = 'ms-teams';
select * from crm_configurations where provider = 'bullhorn'; # 344
SELECT * FROM teams WHERE id = 442; # 14293
select * from users where team_id = 442;
select * from social_accounts sa where sa.sociable_id = 14293;
select * from invitations where team_id = 442;
# [PASSWORD_DOTS]
SELECT * FROM users WHERE email LIKE '%[EMAIL]%'; # 14022
SELECT * FROM teams WHERE id = 429;
select * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);
select * from activities where opportunity_id in (4340436,4353519);
select * from transcription where activity_id IN (25630961,25381771);
select * from generic_ai_prompts where subject_id IN (4353519);
SELECT
a.id as activity_id,
a.opportunity_id,
a.type as activity_type,
a.language,
CONCAT(a.title, a.description) AS mail_content,
e.from AS mail_from,
e.to AS mail_to,
e.subject AS mail_subject,
e.body AS mail_body,
p.type as prompt_type,
p.status as prompt_status,
p.content AS prompt_content,
a.actual_start_time as created_at
FROM activities a
LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL
LEFT JOIN email_messages e ON a.id = e.activity_id
WHERE a.actual_start_time > '2024-01-01 00:00:00'
AND a.opportunity_id IN (4353519)
AND a.status IN ('completed', 'received', 'delivered')
AND a.deleted_at IS NULL
AND a.type NOT IN ('sms-inbound', 'sms-outbound')
ORDER BY a.opportunity_id ASC, a.id ASC;
SELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293
SELECT * FROM teams WHERE id = 442;
SELECT * FROM crm_configurations WHERE id = 344;
select * from team_features where team_id = 442;
select * from groups where team_id = 442;
select * from playbooks where team_id = 442;
select * from playbook_categories where playbook_id = 1729;
select * from crm_fields where crm_configuration_id = 344 and id = 172024;
SELECT * FROM crm_field_values WHERE crm_field_id = 172024;
select * from crm_layouts where crm_configuration_id = 344;
select * from playbook_layouts where playbook_id = 1729;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444
select s.*
# , s.sent_at, u.name, a.*
from activity_summary_logs s
inner join activities a on a.id = s.activity_id
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 356
and s.sent_at > date_sub(now(), interval 60 day)
order by a.actual_end_time desc;
select * from activities a
# inner join activity_summary_logs s on s.activity_id = a.id
where a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)
# and a.crm_provider_id is not null
# and provider <> 'ringcentral'
and status = 'completed'
order by a.actual_end_time desc;
select * from teams order by id desc; # 17328, 32, 17830, [EMAIL]
SELECT * FROM users;
SELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active
SELECT * FROM teams WHERE id = 260;
select * from team_settings where team_id = 260;
select * from crm_configurations where team_id = 260;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 356;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;
select * from accounts where crm_configuration_id = 221 order by id desc; # 7000
select * from leads where crm_configuration_id = 221 order by id desc; # 0
select * from contacts where crm_configuration_id = 221 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 221 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 221;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 221 order by id desc;
select * from stages where crm_configuration_id = 221 order by id desc;
select * from accounts where crm_configuration_id = 356 order by id desc; # 7000
select * from leads where crm_configuration_id = 356 order by id desc; # 0
select * from contacts where crm_configuration_id = 356 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 356 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 356;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 356 order by id desc;
select * from stages where crm_configuration_id = 356 order by id desc;
select * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)
select * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)
select * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4
select ce.* from calendars c
join users u on c.user_id = u.id
join calendar_events ce on c.id = ce.calendar_id
where u.team_id = 260
and (ce.start_time > '2025-02-21 00:00:00')
;
# calendar events 1207
#
select * from opportunities where team_id = 260;
SELECT * FROM crm_field_data WHERE object_id = 4696496;
select * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;
select * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')
# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0
and created_at > '2024-03-01 00:00:00'
order by id desc; # 880 000, ringcentral, avaya
SELECT * FROM participants WHERE activity_id = 26371744;
# all activities 942 000 +
# conference 7385 - scheduled 984 - external 343
select * from activities where id = 26321812;
select * from participants where activity_id = 26321812;
select * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);
select * from leads where id in (720428,689175,731546,645866,621037);
select * from users where id = 13841;
select * from opportunities where user_id = 9541;
select * from stages where id = 15900;
select * from accounts where
# id IN (4160055,5053725,4965303,4896434)
id in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)
;
select * from activities where id = 26654935;
SELECT * FROM opportunities WHERE id = 4803458;
SELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;
SELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time
FROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);
SELECT DISTINCT
o.id, o.stage_id, s.name, a.title,
a.*
FROM activities a
# INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
INNER JOIN groups g ON u.group_id = g.id
INNER JOIN opportunities o ON a.opportunity_id = o.id
INNER JOIN stages s ON o.stage_id = s.id
WHERE
a.crm_configuration_id = 356
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 13841
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')
AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
)
)
AND (
# s.id = 15900
s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')
OR s.uuid IS NULL -- Include records without opportunity stage
)
ORDER BY a.actual_end_time DESC;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, [EMAIL]
SELECT * FROM users WHERE team_id = 190;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 190
and sa.provider = 'hubspot';
select * from role_user where user_id = 8474;
select * from crm_configurations where provider = 'bullhorn';
SELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;
SELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;
SELECT * FROM opportunities WHERE id = 4732493;
select * from activities where opportunity_id = 4732493;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 443; # 358, 14315, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 443;
SELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id
FROM activities AS a
JOIN stages AS s ON a.stage_id = s.id
JOIN users AS u ON u.id = a.user_id
JOIN teams AS t ON t.id = s.team_id
WHERE u.team_id <> s.team_id and t.id > 135;
SELECT
crm_configuration_id,
crm_provider_id,
COUNT(*) as duplicate_count,
GROUP_CONCAT(id) as stage_ids,
GROUP_CONCAT(name) as stage_names
FROM stages
GROUP BY crm_configuration_id, crm_provider_id
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC;
select * from stages where id IN (14898,14907);
select * from business_processes;
SELECT *
FROM crm_configurations
WHERE team_id IN (
SELECT team_id
FROM crm_configurations
GROUP BY team_id
HAVING COUNT(*) > 1
)
ORDER BY team_id;
SELECT *
FROM teams
WHERE crm_id IN (
SELECT crm_id
FROM teams
GROUP BY crm_id
HAVING COUNT(*) > 1
)
ORDER BY crm_id;
# [PASSWORD_DOTS]
select * from crm_configurations where provider = 'integration-app';
SELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 [EMAIL]
select * from activities where crm_configuration_id = 358 order by actual_end_time desc;
select id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;
select * from team_features where team_id = 358;
select * from activity_summary_logs;
select * from teams where id = 406;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, [EMAIL]
select * from activities where crm_configuration_id = 202 order by actual_end_time desc;
SELECT * FROM users where id = 14637;
SELECT * FROM teams where id = 267;
SELECT * FROM groups where id = 1118;
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM activities
WHERE crm_configuration_id = 202
AND status IN ('completed', 'failed')
AND recording_state != 'stopped'
AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
AND (is_private = 0 OR user_id = 14637)
AND (
(
actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
) OR (
actual_start_time IS NULL
AND type IN ('sms-outbound', 'sms-inbound')
AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND NOT EXISTS (
SELECT 1
FROM tracks
WHERE
tracks.activity_id = activities.id
AND tracks.type IN ('audio', 'video')
)
ORDER BY actual_end_time DESC;
SELECT DISTINCT
a.*
FROM activities a
INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
WHERE
a.crm_configuration_id = 202
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 14637
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND a.user_id = 14637
)
)
ORDER BY a.actual_end_time DESC
;
SELECT DISTINCT a.*
FROM activities a
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams t ON u.team_id = t.id
# INNER JOIN tracks tr ON a.id = tr.activity_id
# INNER JOIN groups g ON u.group_id = g.id
WHERE 1=1
AND t.id = 267
# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND tr.type NOT IN ('audio', 'video')
AND (
a.is_private = 0
OR a.user_id = 14637
)
AND (
(a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')
OR (
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'
)
)
# and NOT EXISTS (
# SELECT 1
# FROM tracks t
# WHERE t.activity_id = a.id
# AND t.type IN ('audio', 'video')
# )
ORDER BY a.actual_end_time DESC;
SELECT * FROM tracks WHERE activity_id = 26485995;
select a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 202
# and a.is_internal = 0
and (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type IN ("softphone","softphone-inbound","conference","sms-inbound")
and a.status IN ('completed', 'failed')
# and a.external_id is not null
order by a.actual_end_time desc;
select * from activities a where a.crm_configuration_id = 202
and a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'
# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM teams WHERE name LIKE '%Tourlane%';
SELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_field_data WHERE crm_field_id = 98809;
select * from users where status = 1 AND timezone = 'MDT';
select * from opportunities where id = 3769814;
select * from deal_risks where opportunity_id = 3769814;
select cp.* from crm_profiles cp
join users u on cp.user_id = u.id
join crm_configurations crm on cp.crm_configuration_id = crm.id
where crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';
select * from crm_fields where id = 154575;
select * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';
SELECT * FROM teams WHERE id = 176; # crm 148
select * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;
select * from activity_providers where provider = 'amazon-connect';
select * from crm_fields cf
join crm_configurations crm on crm.id = cf.crm_configuration_id
where crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');
# [PASSWORD_DOTS]
SELECT * FROM users WHERE id IN (15415, 15418);
SELECT * FROM groups WHERE id IN (1805,1806);
SELECT * FROM playbooks WHERE id = 1860;
SELECT * FROM playbook_categories WHERE id = 38634;
SELECT * FROM crm_fields WHERE id = 189962;
SELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 [EMAIL]
SELECT * FROM crm_profiles WHERE user_id = 15415;
SELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';
select * from sidekick_settings where team_id = 472;
SELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418
SELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, [EMAIL]
select * from crm_configurations where id = 218;
SELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765
SELECT * FROM users WHERE id IN (13232, 13230);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
0057R00000EPL5HQAX Inez Ekblad
1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur
SELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);
############################################################################################
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id IN (94491,94493,94498);
SELECT * FROM users WHERE id = 13658;
SELECT * FROM teams WHERE id = 109;
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, [EMAIL]
SELECT * FROM stages WHERE crm_configuration_id = 390;
select * from business_processes where team_id = 481 and crm_configuration_id = 390;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 481
and sa.provider = 'salesforce';
SELECT * FROM users WHERE id = 15780; # team 462
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 462
and sa.provider = 'hubspot';
select * from teams where id = 495;
SELECT * FROM users WHERE id = 15794;
select * from social_accounts where sociable_id = 15794;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752
SELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794
SELECT * FROM activities WHERE crm_configuration_id = 407
and status = 'completed' and type = 'conference'
order by id desc;
select ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id
join permission_role pr on pr.role_id = ru.role_id
join permissions p on p.id = pr.permission_id
where team_id = 495 and p.name IN ('dial');
select * from permission_role;
select * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;
SELECT * FROM activities WHERE id = 29512773;
SELECT * FROM activities WHERE id IN (29042721,28991325,29002874);
SELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 407
# and a.id IN (29042721,28991325,29002874);
SELECT * FROM users WHERE id = 15794;
SELECT * FROM users WHERE team_id = 495;
SELECT * FROM social_accounts WHERE sociable_id = 15794;
SELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';
SELECT * FROM contacts WHERE team_id = 495;
SELECT * FROM leads WHERE team_id = 495;
SELECT * FROM accounts WHERE team_id = 495;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 407;
SELECT * FROM crm_fields WHERE crm_configuration_id = 407;
SELECT * FROM crm_configurations WHERE id = 407;
SELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'
and user_id IS NOT NULL and is_closed = 1 and is_won = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103
SELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064
SELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 325
and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085
SELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733
SELECT * FROM activity_summary_logs where activity_id = 28719733;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444
SELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';
SELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630
select * from activities where crm_configuration_id = 356 and lead_id = 841732;
SELECT * from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 356;
select * from activities where crm_configuration_id = 356
and actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'
order by id desc;
select * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;
select * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from team_features where team_id = 260;
select * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;
select * from crm_fields;
select * from crm_layout_entities;
SELECT * FROM teams WHERE name LIKE '%Optable%';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id in (94491,94493,94498);
select * from teams where crm_id IS NULL;
SELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;
# [PASSWORD_DOTS]
select * from team_domains where team_id = 399;
SELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207
select * from calendar_events where id = 5163781;
SELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896
SELECT * FROM participants WHERE activity_id = 29443896;
select * from contacts where crm_configuration_id = 318 and email = '[EMAIL]';
select * from leads where crm_configuration_id = 318 and email = '[EMAIL]';
select * from activities where user_id = 14937 order by created_at ;
select * from users where id = 14937;
select * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';
select * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';
select * from activities a join participants p on a.id = p.activity_id
where crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';
# [PASSWORD_DOTS]
SELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';
SELECT * FROM opportunities WHERE team_id = 379 order by id desc;
SELECT * FROM teams WHERE id = 379;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379 and sociable_id = 13852
and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE id = 307;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 307;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;
SELECT * FROM crm_fields WHERE crm_configuration_id = 307
and id IN (144750,144855,145158,155227);
SELECT * FROM activities;
select * from activities
where created_at > '2025-07-01 00:00:00'
# and created_at < '2025-08-01 00:00:00'
and type not in ('email-outbound', 'email-inbound')
and account_id is null
and contact_id is null
and lead_id is null
and opportunity_id is not null
;
SELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);
SELECT * FROM crm_configurations WHERE id in (335,301,200);
select * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';
SELECT * FROM teams WHERE name LIKE '%Resights%';
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_configurations where provider = 'bullhorn'; # 344
select * from teams where id IN (442);
select * from activities
where crm_configuration_id = 177
and provider = 'amazon-connect'
order by id desc;
# and source <> 'gong';
select * from activity_providers where provider = 'amazon-connect';
SELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;
select * from crm_configurations where store_transcript = 1;
SELECT * FROM teams WHERE id IN (80);
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 277
and sa.provider = 'salesforce';
select * from activities where crm_configuration_id = 213 and account_id = 2511502;
select * from crm_configurations where id = 213;
SELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604
SELECT * FROM participants WHERE activity_id = 33981604;
SELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 431
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223
select * from activity_summary_logs where activity_id = 33997223;
select * from activity_notes where activity_id =...
|
53463
|
|
53466
|
1157
|
38
|
2026-04-20T08:13:44.958433+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-20/1776 /Users/lukas/.screenpipe/data/data/2026-04-20/1776672824958_m2.jpg...
|
PhpStorm
|
faVsco.js – TrackAutomatedReportGeneratedEvent.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests passed: 7
text/html
text/html
text/html
Proj Tests passed: 7
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
class TrackAutomatedReportGeneratedEvent implements ShouldQueue
{
use InteractsWithQueue;
private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';
private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';
public string $queue = Constants::QUEUE_DELAYABLE;
public function __construct(
private readonly UserPilotClient $userPilotClient,
private readonly AutomatedReportsService $automatedReportsService,
) {
}
public function handle(AutomatedReportGenerated $event): void
{
if (config('services.userpilot.token') === null) {
return;
}
$automatedReport = $event->automatedReport;
$payload = $this->buildPayload($automatedReport);
$eventName = $this->resolveEventName($automatedReport);
try {
foreach ($this->resolveUsers($automatedReport) as $user) {
$this->userPilotClient->track($user, $eventName, $payload);
}
} catch (GuzzleException $e) {
$this->release(3600);
}
}
/**
* @return array<UserContract>
*/
private function resolveUsers(AutomatedReport $automatedReport): array
{
if ($automatedReport->isAskJiminnyReport()) {
$creator = $automatedReport->getCreator();
return $creator !== null ? [$creator] : [];
}
return $this->automatedReportsService->getRecipientUserObjects($automatedReport);
}
private function buildPayload(AutomatedReport $automatedReport): array
{
return [
'report_type' => $automatedReport->getType(),
'frequency' => $automatedReport->getFrequency(),
];
}
private function resolveEventName(AutomatedReport $automatedReport): string
{
if ($automatedReport->isAskJiminnyReport()) {
return self::EVENT_NAME_ASK_JIMINNY_REPORT;
}
return self::EVENT_NAME_AUTOMATED_REPORT;
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
27
9
23
3
105
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM team_features where team_id = 1;
SELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922
SELECT * FROM users WHERE team_id = 340; # 12015
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 340
and sa.provider = 'salesforce';
# and sa.provider = 'salesloft';
select * from crm_fields where crm_configuration_id = 270 and object_type = 'event';
# 125558 - Event Type - Event_Type__c
# 125552 - Event Status - Event_Status__c
SELECT * FROM sidekick_settings WHERE team_id = 340;
SELECT * FROM crm_field_values WHERE crm_field_id in (125552);
select * from activities where crm_configuration_id = 270
and type = 'conference' and crm_provider_id IS NOT NULL
and actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;
SELECT * FROM activities WHERE id = 20871677;
SELECT * FROM crm_field_data WHERE activity_id = 20871677;
select * from crm_layouts where crm_configuration_id = 270;
select * from crm_layout_entities where crm_layout_id in (886,887);
SELECT * FROM crm_configurations WHERE id = 270;
select * from playbooks where team_id = 340; # 1514
select * from groups where team_id = 340;
SELECT * FROM crm_fields WHERE id IN (125393, 125401);
select g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g
join playbooks p on g.playbook_id = p.id
join crm_fields f on p.activity_field_id = f.id
where g.team_id = 340;
SELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716
select * from crm_field_data where object_id = 20448716;
select * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008
select * from opportunities where team_id = 343;
select * from opportunities where team_id = 343 and crm_provider_id = '18099102526';
select * from opportunities where team_id = 343 and account_id = 945217482;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
select * from accounts where team_id = 343 order by name asc;
select * from stages where crm_configuration_id = 273 and type = 'opportunity';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143
SELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;
SELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';
SELECT * FROM activities WHERE id = 20717903;
select * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 353
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;
# id: 20940638, user: 12022, contact: 5305871
SELECT * FROM activity_summary_logs WHERE activity_id = 20940638;
select * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 345
and sa.provider = 'hubspot';
select * from users where team_id = 345 and id = 12022;
SELECT * FROM crm_profiles WHERE user_id = 12022;
SELECT * FROM participants WHERE activity_id = 20940638;
SELECT * FROM users u
JOIN crm_profiles cp ON u.id = cp.user_id
WHERE u.team_id = 345;
select * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871
select * from team_features where team_id = 345;
SELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197
SELECT * FROM participants WHERE activity_id = 20897406;
SELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912
SELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';
SELECT * FROM activities WHERE id = 20946641;
SELECT * FROM crm_profiles WHERE user_id = 10211;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, [EMAIL]
SELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';
select * from stages where crm_configuration_id = 97 and type = 'opportunity';
select * from opportunities where team_id = 120;
select * from crm_configurations crm join teams t on crm.id = t.crm_id
where 1=1
AND t.current_billing_plan IS NOT NULL
AND crm.auto_sync_activity = 0
and crm.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 270
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956
SELECT * FROM crm_profiles WHERE user_id = 11446;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, [EMAIL]
select * from playbooks where team_id = 372;
select * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340
SELECT * FROM crm_field_values WHERE crm_field_id = 141340;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 372
and sa.provider = 'salesforce';
select * from crm_profiles where crm_configuration_id = 300;
SELECT * FROM crm_configurations WHERE team_id = 372;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,[EMAIL]
SELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756
select * from crm_field_data where object_id = 3207756;
SELECT * FROM crm_fields WHERE id = 111834;
select f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value
FROM crm_fields f
JOIN crm_field_data fd ON f.id = fd.crm_field_id
WHERE f.crm_configuration_id = 242
AND f.object_type = 'opportunity'
AND fd.object_id IN (3207756)
ORDER BY fd.object_id, fd.updated_at;
SELECT * FROM crm_configurations WHERE auto_connect = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,[EMAIL]
select * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id
where g.team_id = 187;
select * from `groups` where team_id = 187;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 187
and sa.provider = 'salesforce';
# Destination - 98870 - Destination__c
# Stage - 79014 - StageName
# Land Arrangement - 98856 - Land_Arrangement__c
# Flight - 98848 - Flight__c
# Last activity date - 98812 - LastActivityDate
# Last modified date - 98809 - LastModifiedDate
# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c
# next call - 98864 - Next_Call__c
select * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
select * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';
select * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;
select * from activities where opportunity_id = 3538248;
SELECT * FROM crm_profiles WHERE user_id = 8150;
select * from deal_risks where opportunity_id = 3538248;
select * from teams where crm_id IS NULL;
SELECT opp.id AS opportunity_id,
u.group_id AS group_id,
MAX(
CASE
WHEN a.type IN ("sms-inbound", "sms-outbound") THEN a.created_at
ELSE a.actual_end_time
END) as last_date
FROM opportunities opp
left join activities a on a.opportunity_id = opp.id
inner join users u on opp.user_id = u.id
where opp.user_id IN (9951)
AND opp.is_closed = 0
and a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL
group by opp.id;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_profiles WHERE crm_configuration_id = 301;
SELECT * FROM contacts WHERE id = 6612363;
SELECT * FROM accounts WHERE id = 4235676;
SELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;
select * from opportunity_stages where opportunity_id = 4503759;
# SELECT * FROM opportunities WHERE id = 4569937;
select * from activities where crm_configuration_id = 301;
SELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370
SELECT * FROM participants WHERE activity_id = 26330370;
SELECT * FROM teams WHERE id = 375;
select * from playbooks where team_id = 375;
select * from stages where crm_configuration_id = 301 and type = 'opportunity';
select * from teams;
select * from contact_roles;
SELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';
select * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;
SELECT * FROM crm_field_data WHERE object_id = 3771706;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'
and crm_provider_id LIKE "%traffic_light%";
SELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);
SELECT fd.* FROM opportunities o
JOIN crm_field_data fd ON o.id = fd.object_id
WHERE o.team_id = 343
# and o.user_id IS NOT NULL
and fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)
and fd.value != ''
order by value desc
# group by o.id
;
SELECT * FROM opportunities WHERE id = 3769843;
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, [EMAIL]
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,[EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839
SELECT * FROM opportunities WHERE id = 3855992;
SELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988
SELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894
SELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';
select * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507
SELECT * FROM crm_field_data WHERE object_id = 5874411;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379
and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793
select * from generic_ai_prompts where subject_id = 3537793;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, [EMAIL]
SELECT * FROM crm_configurations WHERE id = 97;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 97;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;
SELECT * FROM crm_fields WHERE id = 32682;
select cfd.value, o.* from opportunities o
join crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682
where team_id = 120
and cfd.value != ''
;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 120
and sa.provider = 'salesforce';
select * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';
SELECT * FROM crm_field_data WHERE object_id = 2313439;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 410;
SELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';
select * from scorecards where team_id = 410;
select * from scorecard_rules;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, [EMAIL]
select * from activities a
join opportunities o on a.opportunity_id = o.id
join users u on o.user_id = u.id
where a.crm_configuration_id = 177 and a.type LIKE '%email-out%'
# and a.actual_end_time > '2024-12-16 00:00:00'
# and o.remotely_created_at > '2024-12-01 00:00:00'
# and u.group_id = 1014
and u.id = 9021
order by a.id desc;
SELECT * FROM opportunities WHERE id in (3981384,4017346);
SELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);
select * from users where id = 9021;
select * from inboxes where user_id = 9021;
select * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';
select * from email_messages where team_id = 220
and orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'
and subject LIKE '%Personal%'
# and 'from' = '[EMAIL]'
;
select * from activities a
join opportunities o on a.opportunity_id = o.id
where a.user_id = 9021 and a.type LIKE '%email-out%'
and a.actual_end_time > '2024-12-18 00:00:00'
and o.user_id IS NOT NULL
and o.remotely_created_at > '2024-12-01 00:00:00'
order by a.id desc;
SELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;
select * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;
select * from team_settings where name IN ('useCloseDate');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 104
and sa.provider = 'hubspot';
select * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'
select * from teams where crm_id IS NULL;
select t.name as 'team', u.name as 'owner', u.email, u.phone
from teams t
join activity_providers ap on t.id = ap.team_id
join users u on t.owner_id = u.id
where 1=1
and t.status = 'active'
and ap.is_enabled = 1
# and u.status = 1
and ap.provider = 'ms-teams';
select * from crm_configurations where provider = 'bullhorn'; # 344
SELECT * FROM teams WHERE id = 442; # 14293
select * from users where team_id = 442;
select * from social_accounts sa where sa.sociable_id = 14293;
select * from invitations where team_id = 442;
# [PASSWORD_DOTS]
SELECT * FROM users WHERE email LIKE '%[EMAIL]%'; # 14022
SELECT * FROM teams WHERE id = 429;
select * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);
select * from activities where opportunity_id in (4340436,4353519);
select * from transcription where activity_id IN (25630961,25381771);
select * from generic_ai_prompts where subject_id IN (4353519);
SELECT
a.id as activity_id,
a.opportunity_id,
a.type as activity_type,
a.language,
CONCAT(a.title, a.description) AS mail_content,
e.from AS mail_from,
e.to AS mail_to,
e.subject AS mail_subject,
e.body AS mail_body,
p.type as prompt_type,
p.status as prompt_status,
p.content AS prompt_content,
a.actual_start_time as created_at
FROM activities a
LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL
LEFT JOIN email_messages e ON a.id = e.activity_id
WHERE a.actual_start_time > '2024-01-01 00:00:00'
AND a.opportunity_id IN (4353519)
AND a.status IN ('completed', 'received', 'delivered')
AND a.deleted_at IS NULL
AND a.type NOT IN ('sms-inbound', 'sms-outbound')
ORDER BY a.opportunity_id ASC, a.id ASC;
SELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293
SELECT * FROM teams WHERE id = 442;
SELECT * FROM crm_configurations WHERE id = 344;
select * from team_features where team_id = 442;
select * from groups where team_id = 442;
select * from playbooks where team_id = 442;
select * from playbook_categories where playbook_id = 1729;
select * from crm_fields where crm_configuration_id = 344 and id = 172024;
SELECT * FROM crm_field_values WHERE crm_field_id = 172024;
select * from crm_layouts where crm_configuration_id = 344;
select * from playbook_layouts where playbook_id = 1729;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444
select s.*
# , s.sent_at, u.name, a.*
from activity_summary_logs s
inner join activities a on a.id = s.activity_id
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 356
and s.sent_at > date_sub(now(), interval 60 day)
order by a.actual_end_time desc;
select * from activities a
# inner join activity_summary_logs s on s.activity_id = a.id
where a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)
# and a.crm_provider_id is not null
# and provider <> 'ringcentral'
and status = 'completed'
order by a.actual_end_time desc;
select * from teams order by id desc; # 17328, 32, 17830, [EMAIL]
SELECT * FROM users;
SELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active
SELECT * FROM teams WHERE id = 260;
select * from team_settings where team_id = 260;
select * from crm_configurations where team_id = 260;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 356;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;
select * from accounts where crm_configuration_id = 221 order by id desc; # 7000
select * from leads where crm_configuration_id = 221 order by id desc; # 0
select * from contacts where crm_configuration_id = 221 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 221 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 221;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 221 order by id desc;
select * from stages where crm_configuration_id = 221 order by id desc;
select * from accounts where crm_configuration_id = 356 order by id desc; # 7000
select * from leads where crm_configuration_id = 356 order by id desc; # 0
select * from contacts where crm_configuration_id = 356 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 356 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 356;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 356 order by id desc;
select * from stages where crm_configuration_id = 356 order by id desc;
select * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)
select * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)
select * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4
select ce.* from calendars c
join users u on c.user_id = u.id
join calendar_events ce on c.id = ce.calendar_id
where u.team_id = 260
and (ce.start_time > '2025-02-21 00:00:00')
;
# calendar events 1207
#
select * from opportunities where team_id = 260;
SELECT * FROM crm_field_data WHERE object_id = 4696496;
select * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;
select * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')
# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0
and created_at > '2024-03-01 00:00:00'
order by id desc; # 880 000, ringcentral, avaya
SELECT * FROM participants WHERE activity_id = 26371744;
# all activities 942 000 +
# conference 7385 - scheduled 984 - external 343
select * from activities where id = 26321812;
select * from participants where activity_id = 26321812;
select * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);
select * from leads where id in (720428,689175,731546,645866,621037);
select * from users where id = 13841;
select * from opportunities where user_id = 9541;
select * from stages where id = 15900;
select * from accounts where
# id IN (4160055,5053725,4965303,4896434)
id in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)
;
select * from activities where id = 26654935;
SELECT * FROM opportunities WHERE id = 4803458;
SELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;
SELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time
FROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);
SELECT DISTINCT
o.id, o.stage_id, s.name, a.title,
a.*
FROM activities a
# INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
INNER JOIN groups g ON u.group_id = g.id
INNER JOIN opportunities o ON a.opportunity_id = o.id
INNER JOIN stages s ON o.stage_id = s.id
WHERE
a.crm_configuration_id = 356
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 13841
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')
AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
)
)
AND (
# s.id = 15900
s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')
OR s.uuid IS NULL -- Include records without opportunity stage
)
ORDER BY a.actual_end_time DESC;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, [EMAIL]
SELECT * FROM users WHERE team_id = 190;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 190
and sa.provider = 'hubspot';
select * from role_user where user_id = 8474;
select * from crm_configurations where provider = 'bullhorn';
SELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;
SELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;
SELECT * FROM opportunities WHERE id = 4732493;
select * from activities where opportunity_id = 4732493;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 443; # 358, 14315, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 443;
SELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id
FROM activities AS a
JOIN stages AS s ON a.stage_id = s.id
JOIN users AS u ON u.id = a.user_id
JOIN teams AS t ON t.id = s.team_id
WHERE u.team_id <> s.team_id and t.id > 135;
SELECT
crm_configuration_id,
crm_provider_id,
COUNT(*) as duplicate_count,
GROUP_CONCAT(id) as stage_ids,
GROUP_CONCAT(name) as stage_names
FROM stages
GROUP BY crm_configuration_id, crm_provider_id
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC;
select * from stages where id IN (14898,14907);
select * from business_processes;
SELECT *
FROM crm_configurations
WHERE team_id IN (
SELECT team_id
FROM crm_configurations
GROUP BY team_id
HAVING COUNT(*) > 1
)
ORDER BY team_id;
SELECT *
FROM teams
WHERE crm_id IN (
SELECT crm_id
FROM teams
GROUP BY crm_id
HAVING COUNT(*) > 1
)
ORDER BY crm_id;
# [PASSWORD_DOTS]
select * from crm_configurations where provider = 'integration-app';
SELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 [EMAIL]
select * from activities where crm_configuration_id = 358 order by actual_end_time desc;
select id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;
select * from team_features where team_id = 358;
select * from activity_summary_logs;
select * from teams where id = 406;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, [EMAIL]
select * from activities where crm_configuration_id = 202 order by actual_end_time desc;
SELECT * FROM users where id = 14637;
SELECT * FROM teams where id = 267;
SELECT * FROM groups where id = 1118;
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM activities
WHERE crm_configuration_id = 202
AND status IN ('completed', 'failed')
AND recording_state != 'stopped'
AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
AND (is_private = 0 OR user_id = 14637)
AND (
(
actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
) OR (
actual_start_time IS NULL
AND type IN ('sms-outbound', 'sms-inbound')
AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND NOT EXISTS (
SELECT 1
FROM tracks
WHERE
tracks.activity_id = activities.id
AND tracks.type IN ('audio', 'video')
)
ORDER BY actual_end_time DESC;
SELECT DISTINCT
a.*
FROM activities a
INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
WHERE
a.crm_configuration_id = 202
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 14637
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND a.user_id = 14637
)
)
ORDER BY a.actual_end_time DESC
;
SELECT DISTINCT a.*
FROM activities a
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams t ON u.team_id = t.id
# INNER JOIN tracks tr ON a.id = tr.activity_id
# INNER JOIN groups g ON u.group_id = g.id
WHERE 1=1
AND t.id = 267
# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND tr.type NOT IN ('audio', 'video')
AND (
a.is_private = 0
OR a.user_id = 14637
)
AND (
(a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')
OR (
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'
)
)
# and NOT EXISTS (
# SELECT 1
# FROM tracks t
# WHERE t.activity_id = a.id
# AND t.type IN ('audio', 'video')
# )
ORDER BY a.actual_end_time DESC;
SELECT * FROM tracks WHERE activity_id = 26485995;
select a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 202
# and a.is_internal = 0
and (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type IN ("softphone","softphone-inbound","conference","sms-inbound")
and a.status IN ('completed', 'failed')
# and a.external_id is not null
order by a.actual_end_time desc;
select * from activities a where a.crm_configuration_id = 202
and a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'
# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM teams WHERE name LIKE '%Tourlane%';
SELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_field_data WHERE crm_field_id = 98809;
select * from users where status = 1 AND timezone = 'MDT';
select * from opportunities where id = 3769814;
select * from deal_risks where opportunity_id = 3769814;
select cp.* from crm_profiles cp
join users u on cp.user_id = u.id
join crm_configurations crm on cp.crm_configuration_id = crm.id
where crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';
select * from crm_fields where id = 154575;
select * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';
SELECT * FROM teams WHERE id = 176; # crm 148
select * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;
select * from activity_providers where provider = 'amazon-connect';
select * from crm_fields cf
join crm_configurations crm on crm.id = cf.crm_configuration_id
where crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');
# [PASSWORD_DOTS]
SELECT * FROM users WHERE id IN (15415, 15418);
SELECT * FROM groups WHERE id IN (1805,1806);
SELECT * FROM playbooks WHERE id = 1860;
SELECT * FROM playbook_categories WHERE id = 38634;
SELECT * FROM crm_fields WHERE id = 189962;
SELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 [EMAIL]
SELECT * FROM crm_profiles WHERE user_id = 15415;
SELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';
select * from sidekick_settings where team_id = 472;
SELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418
SELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, [EMAIL]
select * from crm_configurations where id = 218;
SELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765
SELECT * FROM users WHERE id IN (13232, 13230);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
0057R00000EPL5HQAX Inez Ekblad
1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur
SELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);
############################################################################################
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id IN (94491,94493,94498);
SELECT * FROM users WHERE id = 13658;
SELECT * FROM teams WHERE id = 109;
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, [EMAIL]
SELECT * FROM stages WHERE crm_configuration_id = 390;
select * from business_processes where team_id = 481 and crm_configuration_id = 390;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 481
and sa.provider = 'salesforce';
SELECT * FROM users WHERE id = 15780; # team 462
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 462
and sa.provider = 'hubspot';
select * from teams where id = 495;
SELECT * FROM users WHERE id = 15794;
select * from social_accounts where sociable_id = 15794;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752
SELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794
SELECT * FROM activities WHERE crm_configuration_id = 407
and status = 'completed' and type = 'conference'
order by id desc;
select ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id
join permission_role pr on pr.role_id = ru.role_id
join permissions p on p.id = pr.permission_id
where team_id = 495 and p.name IN ('dial');
select * from permission_role;
select * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;
SELECT * FROM activities WHERE id = 29512773;
SELECT * FROM activities WHERE id IN (29042721,28991325,29002874);
SELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 407
# and a.id IN (29042721,28991325,29002874);
SELECT * FROM users WHERE id = 15794;
SELECT * FROM users WHERE team_id = 495;
SELECT * FROM social_accounts WHERE sociable_id = 15794;
SELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';
SELECT * FROM contacts WHERE team_id = 495;
SELECT * FROM leads WHERE team_id = 495;
SELECT * FROM accounts WHERE team_id = 495;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 407;
SELECT * FROM crm_fields WHERE crm_configuration_id = 407;
SELECT * FROM crm_configurations WHERE id = 407;
SELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'
and user_id IS NOT NULL and is_closed = 1 and is_won = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103
SELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064
SELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 325
and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085
SELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733
SELECT * FROM activity_summary_logs where activity_id = 28719733;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444
SELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';
SELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630
select * from activities where crm_configuration_id = 356 and lead_id = 841732;
SELECT * from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 356;
select * from activities where crm_configuration_id = 356
and actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'
order by id desc;
select * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;
select * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from team_features where team_id = 260;
select * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;
select * from crm_fields;
select * from crm_layout_entities;
SELECT * FROM teams WHERE name LIKE '%Optable%';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id in (94491,94493,94498);
select * from teams where crm_id IS NULL;
SELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;
# [PASSWORD_DOTS]
select * from team_domains where team_id = 399;
SELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207
select * from calendar_events where id = 5163781;
SELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896
SELECT * FROM participants WHERE activity_id = 29443896;
select * from contacts where crm_configuration_id = 318 and email = '[EMAIL]';
select * from leads where crm_configuration_id = 318 and email = '[EMAIL]';
select * from activities where user_id = 14937 order by created_at ;
select * from users where id = 14937;
select * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';
select * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';
select * from activities a join participants p on a.id = p.activity_id
where crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';
# [PASSWORD_DOTS]
SELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';
SELECT * FROM opportunities WHERE team_id = 379 order by id desc;
SELECT * FROM teams WHERE id = 379;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379 and sociable_id = 13852
and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE id = 307;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 307;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;
SELECT * FROM crm_fields WHERE crm_configuration_id = 307
and id IN (144750,144855,145158,155227);
SELECT * FROM activities;
select * from activities
where created_at > '2025-07-01 00:00:00'
# and created_at < '2025-08-01 00:00:00'
and type not in ('email-outbound', 'email-inbound')
and account_id is null
and contact_id is null
and lead_id is null
and opportunity_id is not null
;
SELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);
SELECT * FROM crm_configurations WHERE id in (335,301,200);
select * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';
SELECT * FROM teams WHERE name LIKE '%Resights%';
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_configurations where provider = 'bullhorn'; # 344
select * from teams where id IN (442);
select * from activities
where crm_configuration_id = 177
and provider = 'amazon-connect'
order by id desc;
# and source <> 'gong';
select * from activity_providers where provider = 'amazon-connect';
SELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;
select * from crm_configurations where store_transcript = 1;
SELECT * FROM teams WHERE id IN (80);
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 277
and sa.provider = 'salesforce';
select * from activities where crm_configuration_id = 213 and account_id = 2511502;
select * from crm_configurations where id = 213;
SELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604
SELECT * FROM participants WHERE activity_id = 33981604;
SELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 431
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223
select * from activity_summary_logs where activity_id = 33997223;
select * from activity_notes where activity_id =...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests passed: 7","depth":3,"bounds":{"left":0.95079786,"top":0.12849163,"width":0.032247342,"height":0.013567438},"value":"Tests passed: 7","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.95079786,"top":0.12849163,"width":0.032247342,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12732713,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.796875,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TrackAutomatedReportGeneratedEventTest","depth":6,"bounds":{"left":0.8121675,"top":0.019952115,"width":0.103390954,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TrackAutomatedReportGeneratedEventTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.36103722,"top":0.273743,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.37034574,"top":0.273743,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.38031915,"top":0.273743,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.38929522,"top":0.27214685,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.39660904,"top":0.27214685,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\n\nclass TrackAutomatedReportGeneratedEvent implements ShouldQueue\n{\n use InteractsWithQueue;\n\n private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';\n private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';\n\n public string $queue = Constants::QUEUE_DELAYABLE;\n\n public function __construct(\n private readonly UserPilotClient $userPilotClient,\n private readonly AutomatedReportsService $automatedReportsService,\n ) {\n }\n\n public function handle(AutomatedReportGenerated $event): void\n {\n if (config('services.userpilot.token') === null) {\n return;\n }\n\n $automatedReport = $event->automatedReport;\n $payload = $this->buildPayload($automatedReport);\n\n $eventName = $this->resolveEventName($automatedReport);\n\n try {\n foreach ($this->resolveUsers($automatedReport) as $user) {\n $this->userPilotClient->track($user, $eventName, $payload);\n }\n } catch (GuzzleException $e) {\n $this->release(3600);\n }\n }\n\n /**\n * @return array<UserContract>\n */\n private function resolveUsers(AutomatedReport $automatedReport): array\n {\n if ($automatedReport->isAskJiminnyReport()) {\n $creator = $automatedReport->getCreator();\n\n return $creator !== null ? [$creator] : [];\n }\n\n return $this->automatedReportsService->getRecipientUserObjects($automatedReport);\n }\n\n private function buildPayload(AutomatedReport $automatedReport): array\n {\n return [\n 'report_type' => $automatedReport->getType(),\n 'frequency' => $automatedReport->getFrequency(),\n ];\n }\n\n private function resolveEventName(AutomatedReport $automatedReport): string\n {\n if ($automatedReport->isAskJiminnyReport()) {\n return self::EVENT_NAME_ASK_JIMINNY_REPORT;\n }\n\n return self::EVENT_NAME_AUTOMATED_REPORT;\n }\n}","depth":4,"bounds":{"left":0.13597074,"top":0.27055067,"width":0.26728722,"height":0.72944933},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Listeners\\AutomatedReports\\UserPilot;\n\nuse GuzzleHttp\\Exception\\GuzzleException;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Events\\AutomatedReports\\AutomatedReportGenerated;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\Contracts\\UserContract;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\UserPilot\\UserPilotClient;\n\nclass TrackAutomatedReportGeneratedEvent implements ShouldQueue\n{\n use InteractsWithQueue;\n\n private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';\n private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';\n\n public string $queue = Constants::QUEUE_DELAYABLE;\n\n public function __construct(\n private readonly UserPilotClient $userPilotClient,\n private readonly AutomatedReportsService $automatedReportsService,\n ) {\n }\n\n public function handle(AutomatedReportGenerated $event): void\n {\n if (config('services.userpilot.token') === null) {\n return;\n }\n\n $automatedReport = $event->automatedReport;\n $payload = $this->buildPayload($automatedReport);\n\n $eventName = $this->resolveEventName($automatedReport);\n\n try {\n foreach ($this->resolveUsers($automatedReport) as $user) {\n $this->userPilotClient->track($user, $eventName, $payload);\n }\n } catch (GuzzleException $e) {\n $this->release(3600);\n }\n }\n\n /**\n * @return array<UserContract>\n */\n private function resolveUsers(AutomatedReport $automatedReport): array\n {\n if ($automatedReport->isAskJiminnyReport()) {\n $creator = $automatedReport->getCreator();\n\n return $creator !== null ? [$creator] : [];\n }\n\n return $this->automatedReportsService->getRecipientUserObjects($automatedReport);\n }\n\n private function buildPayload(AutomatedReport $automatedReport): array\n {\n return [\n 'report_type' => $automatedReport->getType(),\n 'frequency' => $automatedReport->getFrequency(),\n ];\n }\n\n private function resolveEventName(AutomatedReport $automatedReport): string\n {\n if ($automatedReport->isAskJiminnyReport()) {\n return self::EVENT_NAME_ASK_JIMINNY_REPORT;\n }\n\n return self::EVENT_NAME_AUTOMATED_REPORT;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.40492022,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.41356382,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.4245346,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.4331782,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.4418218,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.45279256,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.4637633,"top":0.09896249,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.49035904,"top":0.09896249,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.5013298,"top":0.09896249,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.69913566,"top":0.09896249,"width":0.02825798,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"27","depth":4,"bounds":{"left":0.6565825,"top":0.123703115,"width":0.009973404,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.66855055,"top":0.123703115,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"23","depth":4,"bounds":{"left":0.67852396,"top":0.123703115,"width":0.010305851,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.69082445,"top":0.123703115,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"105","depth":4,"bounds":{"left":0.70079786,"top":0.123703115,"width":0.011968086,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7144282,"top":0.12210695,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.72174203,"top":0.12210695,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT * FROM team_features where team_id = 1;\n\nSELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922\nSELECT * FROM users WHERE team_id = 340; # 12015\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 340\nand sa.provider = 'salesforce';\n# and sa.provider = 'salesloft';\n\nselect * from crm_fields where crm_configuration_id = 270 and object_type = 'event';\n# 125558 - Event Type - Event_Type__c\n# 125552 - Event Status - Event_Status__c\n\nSELECT * FROM sidekick_settings WHERE team_id = 340;\n\nSELECT * FROM crm_field_values WHERE crm_field_id in (125552);\n\nselect * from activities where crm_configuration_id = 270\nand type = 'conference' and crm_provider_id IS NOT NULL\nand actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;\n\nSELECT * FROM activities WHERE id = 20871677;\nSELECT * FROM crm_field_data WHERE activity_id = 20871677;\n\nselect * from crm_layouts where crm_configuration_id = 270;\nselect * from crm_layout_entities where crm_layout_id in (886,887);\n\nSELECT * FROM crm_configurations WHERE id = 270;\n\nselect * from playbooks where team_id = 340; # 1514\nselect * from groups where team_id = 340;\nSELECT * FROM crm_fields WHERE id IN (125393, 125401);\n\nselect g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g\njoin playbooks p on g.playbook_id = p.id\njoin crm_fields f on p.activity_field_id = f.id\nwhere g.team_id = 340;\n\nSELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716\nselect * from crm_field_data where object_id = 20448716;\n\nselect * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008\nselect * from opportunities where team_id = 343;\nselect * from opportunities where team_id = 343 and crm_provider_id = '18099102526';\nselect * from opportunities where team_id = 343 and account_id = 945217482;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from accounts where team_id = 343 order by name asc;\n\nselect * from stages where crm_configuration_id = 273 and type = 'opportunity';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143\nSELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;\nSELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';\nSELECT * FROM activities WHERE id = 20717903;\n\nselect * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 353\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, l.atkinson@mwbsolutions.co.uk\nSELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;\n# id: 20940638, user: 12022, contact: 5305871\nSELECT * FROM activity_summary_logs WHERE activity_id = 20940638;\nselect * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 345\nand sa.provider = 'hubspot';\n\nselect * from users where team_id = 345 and id = 12022;\nSELECT * FROM crm_profiles WHERE user_id = 12022;\nSELECT * FROM participants WHERE activity_id = 20940638;\nSELECT * FROM users u\nJOIN crm_profiles cp ON u.id = cp.user_id\nWHERE u.team_id = 345;\n\nselect * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871\n\nselect * from team_features where team_id = 345;\nSELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197\nSELECT * FROM participants WHERE activity_id = 20897406;\n\n\n\nSELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912\nSELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';\n\n\nSELECT * FROM activities WHERE id = 20946641;\nSELECT * FROM crm_profiles WHERE user_id = 10211;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, triger@lunio.ai\nSELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';\nselect * from stages where crm_configuration_id = 97 and type = 'opportunity';\nselect * from opportunities where team_id = 120;\n\n\nselect * from crm_configurations crm join teams t on crm.id = t.crm_id\nwhere 1=1\nAND t.current_billing_plan IS NOT NULL\nAND crm.auto_sync_activity = 0\nand crm.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,james.lewendon@exclaimer.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 270\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956\nSELECT * FROM crm_profiles WHERE user_id = 11446;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, alex.chikly@cygnetise.com\nselect * from playbooks where team_id = 372;\nselect * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340\nSELECT * FROM crm_field_values WHERE crm_field_id = 141340;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 372\nand sa.provider = 'salesforce';\n\nselect * from crm_profiles where crm_configuration_id = 300;\nSELECT * FROM crm_configurations WHERE team_id = 372;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,mfa@planday.com\nSELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756\nselect * from crm_field_data where object_id = 3207756;\nSELECT * FROM crm_fields WHERE id = 111834;\n\nselect f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value\nFROM crm_fields f\nJOIN crm_field_data fd ON f.id = fd.crm_field_id\nWHERE f.crm_configuration_id = 242\nAND f.object_type = 'opportunity'\nAND fd.object_id IN (3207756)\nORDER BY fd.object_id, fd.updated_at;\n\nSELECT * FROM crm_configurations WHERE auto_connect = 1;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,salesforce-admin@tourlane.com\nselect * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id\nwhere g.team_id = 187;\n\nselect * from `groups` where team_id = 187;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 187\nand sa.provider = 'salesforce';\n\n# Destination - 98870 - Destination__c\n# Stage - 79014 - StageName\n# Land Arrangement - 98856 - Land_Arrangement__c\n# Flight - 98848 - Flight__c\n# Last activity date - 98812 - LastActivityDate\n# Last modified date - 98809 - LastModifiedDate\n# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c\n# next call - 98864 - Next_Call__c\n\nselect * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\nselect * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';\nselect * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;\nselect * from activities where opportunity_id = 3538248;\n\nSELECT * FROM crm_profiles WHERE user_id = 8150;\n\nselect * from deal_risks where opportunity_id = 3538248;\n\nselect * from teams where crm_id IS NULL;\n\nSELECT opp.id AS opportunity_id,\n u.group_id AS group_id,\n MAX(\n CASE\n WHEN a.type IN (\"sms-inbound\", \"sms-outbound\") THEN a.created_at\n ELSE a.actual_end_time\n END) as last_date\nFROM opportunities opp\nleft join activities a on a.opportunity_id = opp.id\ninner join users u on opp.user_id = u.id\nwhere opp.user_id IN (9951)\n\nAND opp.is_closed = 0\nand a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL\ngroup by opp.id;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,polly.morphew@cybsafe.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 301;\nSELECT * FROM contacts WHERE id = 6612363;\nSELECT * FROM accounts WHERE id = 4235676;\nSELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;\nselect * from opportunity_stages where opportunity_id = 4503759;\n# SELECT * FROM opportunities WHERE id = 4569937;\n\nselect * from activities where crm_configuration_id = 301;\nSELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370\nSELECT * FROM participants WHERE activity_id = 26330370;\n\nSELECT * FROM teams WHERE id = 375;\nselect * from playbooks where team_id = 375;\n\nselect * from stages where crm_configuration_id = 301 and type = 'opportunity';\n\nselect * from teams;\nselect * from contact_roles;\n\nSELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';\n\nselect * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;\n\nSELECT * FROM crm_field_data WHERE object_id = 3771706;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'\nand crm_provider_id LIKE \"%traffic_light%\";\nSELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);\n\nSELECT fd.* FROM opportunities o\nJOIN crm_field_data fd ON o.id = fd.object_id\nWHERE o.team_id = 343\n# and o.user_id IS NOT NULL\nand fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)\nand fd.value != ''\norder by value desc\n# group by o.id\n;\n\nSELECT * FROM opportunities WHERE id = 3769843;\n\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, salesforce-admin@tourlane.com\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,aswini.mishra@fundingcircle.com\nSELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839\n\n\nSELECT * FROM opportunities WHERE id = 3855992;\n\nSELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988\n\nSELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';\n\nselect * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507\nSELECT * FROM crm_field_data WHERE object_id = 5874411;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379\nand sa.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, nikhil.kumar@mention-me.com\nSELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, salesforce-admin@tourlane.com\nSELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793\nselect * from generic_ai_prompts where subject_id = 3537793;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, triger@lunio.ai\nSELECT * FROM crm_configurations WHERE id = 97;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 97;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;\nSELECT * FROM crm_fields WHERE id = 32682;\n\nselect cfd.value, o.* from opportunities o\njoin crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682\nwhere team_id = 120\nand cfd.value != ''\n;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 120\nand sa.provider = 'salesforce';\n\nselect * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';\nSELECT * FROM crm_field_data WHERE object_id = 2313439;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 410;\nSELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';\nselect * from scorecards where team_id = 410;\nselect * from scorecard_rules;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, aswini.mishra@fundingcircle.com\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\njoin users u on o.user_id = u.id\nwhere a.crm_configuration_id = 177 and a.type LIKE '%email-out%'\n# and a.actual_end_time > '2024-12-16 00:00:00'\n# and o.remotely_created_at > '2024-12-01 00:00:00'\n# and u.group_id = 1014\nand u.id = 9021\norder by a.id desc;\nSELECT * FROM opportunities WHERE id in (3981384,4017346);\nSELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);\n\nselect * from users where id = 9021;\nselect * from inboxes where user_id = 9021;\n\nselect * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';\n\nselect * from email_messages where team_id = 220\nand orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'\nand subject LIKE '%Personal%'\n# and 'from' = 'credit@fundingcircle.com'\n;\n\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\nwhere a.user_id = 9021 and a.type LIKE '%email-out%'\nand a.actual_end_time > '2024-12-18 00:00:00'\nand o.user_id IS NOT NULL\nand o.remotely_created_at > '2024-12-01 00:00:00'\norder by a.id desc;\n\nSELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;\nselect * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;\n\nselect * from team_settings where name IN ('useCloseDate');\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, jfarrell@hurree.co\nSELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 104\nand sa.provider = 'hubspot';\n\nselect * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'\nselect * from teams where crm_id IS NULL;\n\nselect t.name as 'team', u.name as 'owner', u.email, u.phone\nfrom teams t\njoin activity_providers ap on t.id = ap.team_id\njoin users u on t.owner_id = u.id\nwhere 1=1\n and t.status = 'active'\n and ap.is_enabled = 1\n# and u.status = 1\n and ap.provider = 'ms-teams';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nSELECT * FROM teams WHERE id = 442; # 14293\nselect * from users where team_id = 442;\nselect * from social_accounts sa where sa.sociable_id = 14293;\nselect * from invitations where team_id = 442;\n\n# ********************************************************************************************************\nSELECT * FROM users WHERE email LIKE '%nea.liikamaa@eletive.com%'; # 14022\nSELECT * FROM teams WHERE id = 429;\nselect * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);\nselect * from activities where opportunity_id in (4340436,4353519);\n\nselect * from transcription where activity_id IN (25630961,25381771);\nselect * from generic_ai_prompts where subject_id IN (4353519);\n\nSELECT\n a.id as activity_id,\n a.opportunity_id,\n a.type as activity_type,\n a.language,\n CONCAT(a.title, a.description) AS mail_content,\n e.from AS mail_from,\n e.to AS mail_to,\n e.subject AS mail_subject,\n e.body AS mail_body,\n p.type as prompt_type,\n p.status as prompt_status,\n p.content AS prompt_content,\n a.actual_start_time as created_at\nFROM activities a\n LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL\n LEFT JOIN email_messages e ON a.id = e.activity_id\nWHERE a.actual_start_time > '2024-01-01 00:00:00'\n AND a.opportunity_id IN (4353519)\n AND a.status IN ('completed', 'received', 'delivered')\n AND a.deleted_at IS NULL\n AND a.type NOT IN ('sms-inbound', 'sms-outbound')\nORDER BY a.opportunity_id ASC, a.id ASC;\n\nSELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293\nSELECT * FROM teams WHERE id = 442;\nSELECT * FROM crm_configurations WHERE id = 344;\nselect * from team_features where team_id = 442;\nselect * from groups where team_id = 442;\nselect * from playbooks where team_id = 442;\nselect * from playbook_categories where playbook_id = 1729;\nselect * from crm_fields where crm_configuration_id = 344 and id = 172024;\nSELECT * FROM crm_field_values WHERE crm_field_id = 172024;\nselect * from crm_layouts where crm_configuration_id = 344;\nselect * from playbook_layouts where playbook_id = 1729;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444\n\nselect s.*\n# , s.sent_at, u.name, a.*\nfrom activity_summary_logs s\ninner join activities a on a.id = s.activity_id\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 356\nand s.sent_at > date_sub(now(), interval 60 day)\norder by a.actual_end_time desc;\n\nselect * from activities a\n# inner join activity_summary_logs s on s.activity_id = a.id\nwhere a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)\n# and a.crm_provider_id is not null\n# and provider <> 'ringcentral'\nand status = 'completed'\norder by a.actual_end_time desc;\n\nselect * from teams order by id desc; # 17328, 32, 17830, integration-account@jiminny.com\nSELECT * FROM users;\nSELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active\nSELECT * FROM teams WHERE id = 260;\nselect * from team_settings where team_id = 260;\nselect * from crm_configurations where team_id = 260;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 356;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;\n\nselect * from accounts where crm_configuration_id = 221 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 221 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 221 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 221 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 221;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 221 order by id desc;\nselect * from stages where crm_configuration_id = 221 order by id desc;\n\nselect * from accounts where crm_configuration_id = 356 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 356 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 356 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 356 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 356;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 356 order by id desc;\nselect * from stages where crm_configuration_id = 356 order by id desc;\n\nselect * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)\nselect * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)\nselect * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4\nselect ce.* from calendars c\njoin users u on c.user_id = u.id\njoin calendar_events ce on c.id = ce.calendar_id\nwhere u.team_id = 260\nand (ce.start_time > '2025-02-21 00:00:00')\n;\n# calendar events 1207\n#\n\nselect * from opportunities where team_id = 260;\nSELECT * FROM crm_field_data WHERE object_id = 4696496;\n\nselect * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;\nselect * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')\n# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0\nand created_at > '2024-03-01 00:00:00'\norder by id desc; # 880 000, ringcentral, avaya\nSELECT * FROM participants WHERE activity_id = 26371744;\n\n# all activities 942 000 +\n# conference 7385 - scheduled 984 - external 343\n\nselect * from activities where id = 26321812;\nselect * from participants where activity_id = 26321812;\nselect * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);\nselect * from leads where id in (720428,689175,731546,645866,621037);\n\nselect * from users where id = 13841;\nselect * from opportunities where user_id = 9541;\nselect * from stages where id = 15900;\n\nselect * from accounts where\n# id IN (4160055,5053725,4965303,4896434)\nid in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)\n;\n\nselect * from activities where id = 26654935;\nSELECT * FROM opportunities WHERE id = 4803458;\n\nSELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;\nSELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time\nFROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);\n\nSELECT DISTINCT\n o.id, o.stage_id, s.name, a.title,\n a.*\nFROM activities a\n# INNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nINNER JOIN groups g ON u.group_id = g.id\nINNER JOIN opportunities o ON a.opportunity_id = o.id\nINNER JOIN stages s ON o.stage_id = s.id\nWHERE\n a.crm_configuration_id = 356\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 13841\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')\n AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')\n\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n )\n )\n AND (\n# s.id = 15900\n s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')\n OR s.uuid IS NULL -- Include records without opportunity stage\n )\n\nORDER BY a.actual_end_time DESC;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, willsc@leadforensics.com\nSELECT * FROM users WHERE team_id = 190;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 190\nand sa.provider = 'hubspot';\n\nselect * from role_user where user_id = 8474;\n\nselect * from crm_configurations where provider = 'bullhorn';\n\nSELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;\nSELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;\n\nSELECT * FROM opportunities WHERE id = 4732493;\nselect * from activities where opportunity_id = 4732493;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 443; # 358, 14315, andrea.romano@correrenaturale.com\nSELECT * FROM opportunities WHERE team_id = 443;\n\nSELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id\nFROM activities AS a\nJOIN stages AS s ON a.stage_id = s.id\nJOIN users AS u ON u.id = a.user_id\nJOIN teams AS t ON t.id = s.team_id\nWHERE u.team_id <> s.team_id and t.id > 135;\n\n\nSELECT\n crm_configuration_id,\n crm_provider_id,\n COUNT(*) as duplicate_count,\n GROUP_CONCAT(id) as stage_ids,\n GROUP_CONCAT(name) as stage_names\nFROM stages\nGROUP BY crm_configuration_id, crm_provider_id\nHAVING COUNT(*) > 1\nORDER BY duplicate_count DESC;\n\nselect * from stages where id IN (14898,14907);\n\nselect * from business_processes;\n\nSELECT *\nFROM crm_configurations\nWHERE team_id IN (\n SELECT team_id\n FROM crm_configurations\n GROUP BY team_id\n HAVING COUNT(*) > 1\n)\nORDER BY team_id;\n\nSELECT *\nFROM teams\nWHERE crm_id IN (\n SELECT crm_id\n FROM teams\n GROUP BY crm_id\n HAVING COUNT(*) > 1\n)\nORDER BY crm_id;\n\n# ***************************************************************************\nselect * from crm_configurations where provider = 'integration-app';\nSELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 andrea.romano@correrenaturale.com\nselect * from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect * from team_features where team_id = 358;\nselect * from activity_summary_logs;\n\nselect * from teams where id = 406;\n\n# ************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, srv.salesforce@sportfive.com\nselect * from activities where crm_configuration_id = 202 order by actual_end_time desc;\n\nSELECT * FROM users where id = 14637;\nSELECT * FROM teams where id = 267;\nSELECT * FROM groups where id = 1118;\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 202\n AND status IN ('completed', 'failed')\n AND recording_state != 'stopped'\n AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n AND (is_private = 0 OR user_id = 14637)\n AND (\n (\n actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n ) OR (\n actual_start_time IS NULL\n AND type IN ('sms-outbound', 'sms-inbound')\n AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND NOT EXISTS (\n SELECT 1\n FROM tracks\n WHERE\n tracks.activity_id = activities.id\n AND tracks.type IN ('audio', 'video')\n )\nORDER BY actual_end_time DESC;\n\nSELECT DISTINCT\n a.*\nFROM activities a\nINNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nWHERE\n a.crm_configuration_id = 202\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 14637\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND a.user_id = 14637\n )\n )\n\nORDER BY a.actual_end_time DESC\n;\n\nSELECT DISTINCT a.*\nFROM activities a\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams t ON u.team_id = t.id\n# INNER JOIN tracks tr ON a.id = tr.activity_id\n# INNER JOIN groups g ON u.group_id = g.id\nWHERE 1=1\n AND t.id = 267\n# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND tr.type NOT IN ('audio', 'video')\n AND (\n a.is_private = 0\n OR a.user_id = 14637\n )\n AND (\n (a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')\n OR (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'\n )\n )\n# and NOT EXISTS (\n# SELECT 1\n# FROM tracks t\n# WHERE t.activity_id = a.id\n# AND t.type IN ('audio', 'video')\n# )\n\nORDER BY a.actual_end_time DESC;\n\nSELECT * FROM tracks WHERE activity_id = 26485995;\n\nselect a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 202\n# and a.is_internal = 0\nand (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type IN (\"softphone\",\"softphone-inbound\",\"conference\",\"sms-inbound\")\nand a.status IN ('completed', 'failed')\n# and a.external_id is not null\norder by a.actual_end_time desc;\n\nselect * from activities a where a.crm_configuration_id = 202\nand a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'\n# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM teams WHERE name LIKE '%Tourlane%';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_field_data WHERE crm_field_id = 98809;\n\nselect * from users where status = 1 AND timezone = 'MDT';\n\nselect * from opportunities where id = 3769814;\nselect * from deal_risks where opportunity_id = 3769814;\n\nselect cp.* from crm_profiles cp\njoin users u on cp.user_id = u.id\njoin crm_configurations crm on cp.crm_configuration_id = crm.id\nwhere crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';\n\nselect * from crm_fields where id = 154575;\n\nselect * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';\nSELECT * FROM teams WHERE id = 176; # crm 148\nselect * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nselect * from crm_fields cf\njoin crm_configurations crm on crm.id = cf.crm_configuration_id\nwhere crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');\n\n# *********************************************************************************************\nSELECT * FROM users WHERE id IN (15415, 15418);\nSELECT * FROM groups WHERE id IN (1805,1806);\nSELECT * FROM playbooks WHERE id = 1860;\nSELECT * FROM playbook_categories WHERE id = 38634;\nSELECT * FROM crm_fields WHERE id = 189962;\n\nSELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 raza.gilani@vuelio.com\n\nSELECT * FROM crm_profiles WHERE user_id = 15415;\nSELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';\n\nselect * from sidekick_settings where team_id = 472;\n\nSELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418\nSELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415\n\n# *********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, salesforce-integrations@teamtailor.com\nselect * from crm_configurations where id = 218;\nSELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765\nSELECT * FROM users WHERE id IN (13232, 13230);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n0057R00000EPL5HQAX Inez Ekblad\n\n1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur\n\nSELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);\n\n############################################################################################\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id IN (94491,94493,94498);\nSELECT * FROM users WHERE id = 13658;\nSELECT * FROM teams WHERE id = 109;\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, katy.holden@strengthscope.comk\nSELECT * FROM stages WHERE crm_configuration_id = 390;\nselect * from business_processes where team_id = 481 and crm_configuration_id = 390;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 481\nand sa.provider = 'salesforce';\n\n\nSELECT * FROM users WHERE id = 15780; # team 462\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 462\nand sa.provider = 'hubspot';\n\n\nselect * from teams where id = 495;\nSELECT * FROM users WHERE id = 15794;\nselect * from social_accounts where sociable_id = 15794;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752\nSELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794\nSELECT * FROM activities WHERE crm_configuration_id = 407\nand status = 'completed' and type = 'conference'\norder by id desc;\n\nselect ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id\njoin permission_role pr on pr.role_id = ru.role_id\n join permissions p on p.id = pr.permission_id\nwhere team_id = 495 and p.name IN ('dial');\n\nselect * from permission_role;\n\nselect * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;\nSELECT * FROM activities WHERE id = 29512773;\nSELECT * FROM activities WHERE id IN (29042721,28991325,29002874);\n\nSELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 407\n# and a.id IN (29042721,28991325,29002874);\n\nSELECT * FROM users WHERE id = 15794;\nSELECT * FROM users WHERE team_id = 495;\nSELECT * FROM social_accounts WHERE sociable_id = 15794;\nSELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';\nSELECT * FROM contacts WHERE team_id = 495;\nSELECT * FROM leads WHERE team_id = 495;\nSELECT * FROM accounts WHERE team_id = 495;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 407;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 407;\nSELECT * FROM crm_configurations WHERE id = 407;\nSELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'\nand user_id IS NOT NULL and is_closed = 1 and is_won = 1;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103\nSELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064\nSELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');\n\n# *********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 325\nand sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085\nSELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733\nSELECT * FROM activity_summary_logs where activity_id = 28719733;\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444\nSELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';\nSELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630\nselect * from activities where crm_configuration_id = 356 and lead_id = 841732;\n\nSELECT * from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 356;\n\nselect * from activities where crm_configuration_id = 356\nand actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'\norder by id desc;\n\nselect * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;\nselect * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\n\nselect * from team_features where team_id = 260;\nselect * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;\n\nselect * from crm_fields;\nselect * from crm_layout_entities;\n\nSELECT * FROM teams WHERE name LIKE '%Optable%';\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id in (94491,94493,94498);\n\nselect * from teams where crm_id IS NULL;\n\nSELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;\n\n# *************************************************************************************************\nselect * from team_domains where team_id = 399;\nSELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207\n\nselect * from calendar_events where id = 5163781;\nSELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896\nSELECT * FROM participants WHERE activity_id = 29443896;\nselect * from contacts where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\nselect * from leads where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\n\nselect * from activities where user_id = 14937 order by created_at ;\n\nselect * from users where id = 14937;\n\nselect * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';\nselect * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';\n\nselect * from activities a join participants p on a.id = p.activity_id\nwhere crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';\n\n# *************************************************************************************************\nSELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';\nSELECT * FROM opportunities WHERE team_id = 379 order by id desc;\nSELECT * FROM teams WHERE id = 379;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379 and sociable_id = 13852\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE id = 307;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 307;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307\n and id IN (144750,144855,145158,155227);\n\nSELECT * FROM activities;\n\n\nselect * from activities\nwhere created_at > '2025-07-01 00:00:00'\n# and created_at < '2025-08-01 00:00:00'\nand type not in ('email-outbound', 'email-inbound')\nand account_id is null\nand contact_id is null\nand lead_id is null\nand opportunity_id is not null\n;\nSELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);\nSELECT * FROM crm_configurations WHERE id in (335,301,200);\n\nselect * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';\n\nSELECT * FROM teams WHERE name LIKE '%Resights%';\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nselect * from teams where id IN (442);\n\nselect * from activities\nwhere crm_configuration_id = 177\nand provider = 'amazon-connect'\n order by id desc;\n# and source <> 'gong';\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nSELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;\n\n\nselect * from crm_configurations where store_transcript = 1;\nSELECT * FROM teams WHERE id IN (80);\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 277\nand sa.provider = 'salesforce';\n\nselect * from activities where crm_configuration_id = 213 and account_id = 2511502;\n\nselect * from crm_configurations where id = 213;\n\nSELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604\nSELECT * FROM participants WHERE activity_id = 33981604;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 431\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223\nselect * from activity_summary_logs where activity_id = 33997223;\nselect * from activity_notes where activity_id = 33997223;\n\n# ***********************************\nSELECT * FROM teams WHERE name LIKE '%Abode%';\n\n\nselect * from features;\nselect * from teams t\nwhere t.status = 'active'\nand id NOT IN (select team_id from team_features where feature_id = 9)\n;\n\n\nselect * from playbook_layouts where playbook_id = 1725;\nSELECT * FROM activities WHERE uuid_to_bin('65cc283c-4849-49e6-927f-4c281c8fea19') = uuid; # 34297473\nselect * from teams where id = 318;\nselect * from crm_configurations where team_id = 318;\nselect * from playbooks where team_id = 318;\nSELECT * FROM crm_layouts where crm_configuration_id = 381;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1259;\nSELECT * FROM crm_fields WHERE id IN (192938,192936,192939);\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1266;\nSELECT * FROM crm_fields WHERE id IN (192980,192991,192997,192998,193064,193067);\n\nSELECT * FROM activities WHERE uuid_to_bin('a902289b-285c-48eb-9cc2-6ad6c5d938f5') = uuid; # 34297533\n\n\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nSELECT * FROM crm_fields WHERE id IN (131668,131669,131670,131671,131676,131797);\n\nSELECT * FROM teams WHERE name LIKE '%Peripass%'; # 351, 281, 12124\nselect * from crm_layouts where crm_configuration_id = 281;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nselect * from crm_fields where crm_configuration_id = 281 and id in (131668,131669,131670,131671,131676,131797);\nselect * from opportunities where crm_configuration_id = 281;\n\nSELECT * FROM activities WHERE id IN (34211315, 34130075);\nSELECT * FROM crm_field_data WHERE object_id IN (34211315, 34130075);\n\nselect cf.crm_configuration_id, cle.crm_layout_id, cle.id, cf.id from crm_field_data cfd\njoin crm_layout_entities cle on cle.id = cfd.crm_layout_entity_id\njoin crm_fields cf on cle.crm_field_id = cf.id\nwhere cf.deleted_at IS NOT NULL\nGROUP BY cle.id, cf.id;\n\nselect * from crm_layouts where id IN (355);\nselect u.email, t.crm_id, t.* from teams t\njoin users u on u.id = t.owner_id\nwhere crm_id IN (97);\n\nSELECT * FROM crm_fields WHERE id = 96492;\n\nselect * from permissions;\nselect * from permission_role where permission_id = 247;\nselect * from roles;\n\nselect * from migrations;\n# *****************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('291e3c21-11cc-4728-aee7-6e4bedf86d72') = uuid; # 34262174\nSELECT * FROM crm_configurations WHERE id = 301;\nSELECT * FROM teams WHERE id = 343;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from participants where activity_id = 34262174;\n\nselect * from contacts where crm_configuration_id = 301 and id = 6976326;\nselect * from accounts where crm_configuration_id = 301 and id IN (4647626, 4815829); # 30761335403\n\nselect * from activity_summary_logs where activity_id = 34262174;\n\nselect * from users where status = 1 AND timezone = 'EST';\n\n# ****************************************************************************\nSELECT * FROM users WHERE id = 13869;\nSELECT * FROM crm_configurations WHERE id = 320;\nSELECT * FROM teams WHERE id = 401;\n\nSELECT * FROM activities WHERE uuid_to_bin('2228c16f-10be-48d5-90d4-67385219dc01') = uuid; # 29670601\n\nSELECT * FROM accounts WHERE id = 7761483;\nSELECT * FROM opportunities WHERE id = 6051814;\n\nSELECT * FROM teams WHERE name LIKE '%Seedlegals%';\n\n;select * from opportunities where updated_at > '2025-10-11' AND crm_provider_id = '34713761166';\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 177;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 577;\nSELECT * FROM crm_fields WHERE id IN (68458,68459,68480,68497,68524,68530,68554,68618,68662,68781,68810,68898,68981,69049,97467);\n\nSELECT t.id, crm.id, t.name, crm.sync_objects, crm.provider, crm.last_synced_at FROM crm_configurations crm join teams t on t.crm_id = crm.id\nwhere t.status = 'active' AND crm.provider = 'hubspot' AND crm.last_synced_at < '2025-10-22 00:00:00';\n\nSELECT * FROM activities WHERE uuid_to_bin('fa09449f-cba9-496a-b8f3-865cd3c72351') = uuid;\nSELECT * FROM crm_configurations where id = 184;\nSELECT * FROM teams WHERE id = 246;\nSELECT * FROM social_accounts WHERE sociable_id = 9259 and provider = 'hubspot';\n\nSELECT * FROM users WHERE email LIKE '%rhian.old@bud.co.uk%'; # 17700\nSELECT * FROM teams WHERE id = 551;\n\nSELECT * FROM crm_configurations WHERE id = 471;\nSELECT * FROM activities WHERE crm_configuration_id = 471 and crm_provider_id IS NOT NULL;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 471;\nSELECT * FROM crm_fields WHERE id = 307260;\nSELECT * FROM crm_field_values WHERE crm_field_id = 307260;\n\nselect * from crm_layouts where crm_configuration_id = 471;\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1547;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1548;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 551 and sa.provider = 'hubspot';\n\nSELECT * FROM teams WHERE name LIKE '%$PCS%';\n\n# ********************************************************************************************************\nselect * from crm_configurations crm\njoin teams t on t.crm_id = crm.id\nwhere t.status = 'active'\nand crm.provider = 'hubspot';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from crm_configurations where id = 331; # 416\nSELECT * FROM teams WHERE id = 416;\nSELECT * FROM opportunities WHERE team_id = 190;\n\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%';\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 190 and sa.provider = 'hubspot';\n\n\n\nSELECT * FROM teams WHERE name LIKE '%Rapaport%'; # 431, 337\nSELECT * FROM teams where id = 431;\nSELECT * FROM crm_configurations where team_id = 431;\nSELECT * FROM activity_providers where team_id = 431;\nSELECT * FROM activities where crm_configuration_id = 337 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 431 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%BiP%'; # 401, 320\nSELECT * FROM teams where id = 401;\nSELECT * FROM crm_configurations where team_id = 401;\nSELECT * FROM activity_providers where team_id = 401;\nSELECT * FROM activities where crm_configuration_id = 320 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 401 and sa.provider = 'salesforce';\n\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 307; # 379 - Story Terrace Inc , portalId: 3921157\nSELECT * FROM contacts WHERE team_id = 379 and updated_at > '2026-01-31 11:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 379 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; # 563 - LATUS Group (ad94d501-5d09-44fd-878f-ca3a9f8865c3) , portalId: 3904501\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 338; # 432 - Formalize , portalId: 9214205\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 432 and sa.provider = 'hubspot';\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 436; # 519 - Moxso , portalId: 25531989\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 96; # 119 - Nourish Care , portalId: 26617984\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 331; # 416 - The National College , portalId: 7213852\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 308; # 380 - Foodles , portalId: 7723616\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 379; # 471 - imat-uve , portalId: 9177354\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 465; # 545 - Spotler , portalId: 144759271\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 455; # 537 - indevis , portalId: 25666868\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 200; # 265 - Jobadder , portalId: 6426676\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 335; # 429 - Eletive , portalId: 6110563\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 363; # 456 - Global Group , portalId: 8901981\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 297; # 369 - Unbiased , portalId: 9229005\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 353; # 449 - Fuuse , portalId: 25781745\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 487; # 566 - Nimbus , portalId: 39982590\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 487;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1630;\nselect * from crm_fields where crm_configuration_id = 487 and\n(uuid_to_bin('4c6b2971-64d4-45b8-b377-427be758b5a5') = uuid or uuid_to_bin('59e368d8-65a0-4b77-b611-db37c99fbe68') = uuid);\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 420; # 506 - voiio , portalId: 145629154\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 479; # 558 - Momice , portalId: 535962\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 59; # 80 - Storyclash GmbH , portalId: 4268479\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 175; # 203 - Team iAM , portalId: 5534732\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 368; # 460 - OneTouch Health , portalId: 5534732183355\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\n\n\nselect * from users where id = 29643;\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM teams WHERE name LIKE '%Buynomics%'; # 462, 482, 14910\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\n# and description like '%The call focused on understanding Welch%'\norder by id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 462 and sa.provider = 'salesforce';\n\nselect * from contacts where crm_configuration_id = 482 and name = 'Cyndall Hill'; # 15504749\nselect * from contacts where id = 10891096; # 482\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\nand contact_id = 15504749\norder by id desc;\n\nselect * from activities where id = 36793003; # 96cc7bc1-8622-4d27-92f4-baf664fc1a56, 00UOf00000PDdOXMA1\nselect * from transcription where id = 7646782;\nselect * from ai_prompts where transcription_id = 7646782;\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7a8471a3-847e-4822-802b-ddf426bbc252') = uuid; # 37370018\nSELECT * FROM activity_summary_logs WHERE activity_id = 37370018;\nSELECT * FROM teams WHERE id = 555;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 555 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7c17b8aa-09df-4f85-a0f7-51f47afd712d') = uuid; # 37395250\nSELECT * FROM activities WHERE uuid_to_bin('14d60388-260d-494b-aa0d-63fdb1c78026') = uuid; # 37395250\n\nSELECT a.* FROM activities a JOIN crm_configurations c on c.id = a.crm_configuration_id\nwhere a.type IN ('softphone', 'softphone-outbound') and c.provider = 'hubspot'\nand a.provider NOT IN ('hubspot')\n# and a.provider IN ('salesloft')\n# and c.id NOT IN (70)\n# and a.duration > 30\n# and actual_start_time > '2026-02-05 00:00:00'\norder by a.id desc;\n\nSELECT * FROM activities WHERE id = 37549787;\nSELECT * FROM crm_profiles WHERE user_id = 17613;\n\nSELECT * FROM crm_configurations WHERE id = 70;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 93 and sa.provider = 'hubspot';\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations WHERE id = 373; # KPSBremen.de 465 # - no social account\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 465 and sa.provider = 'hubspot';\n\nselect * from crm_configurations where id = 494;\n\nSELECT * FROM teams WHERE name LIKE '%splose%'; # 572, 495, 18708\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 572 and sa.provider = 'pipedrive';\n\nselect * from opportunities where team_id = 572\n# and name like '%Onebright%'\n# and is_closed = 1 and is_won = 0\n order by id desc;\n\n\nselect * from users where deleted_at is null and status = 2;\n\nselect * from contacts where id = 17900517;\nselect * from accounts where id = 10109838;\nselect * from opportunities where id = 6955880;\n\nselect * from opportunity_contacts where opportunity_id = 6955880;\nselect * from opportunity_contacts where contact_id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nSELECT * FROM activities WHERE uuid_to_bin('adcb8331-5988-4353-834e-383a355abba2') = uuid; # 38056424, crm 104659682404\nselect * from teams where id = 456;\nSELECT * FROM crm_configurations WHERE id = 363;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 456 and sa.provider = 'hubspot';\n\nselect * from crm_layouts where crm_configuration_id = 363;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (1203, 1204, 1635);\nSELECT * FROM crm_fields WHERE id IN (181536, 181538, 213455);\n\nSELECT * FROM teams WHERE name LIKE '%Electric%'; # 342, 272, 12767\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and name like 'NORTHUMBRIA POL%'; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 order by remotely_created_at asc; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and updated_at > '2026-01-01 00:00:00';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 272 and object_type = 'opportunity';\nSELECT * FROM crm_field_values WHERE crm_field_id = 127164;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\n\nSELECT * FROM teams WHERE id = 472;\nSELECT * FROM crm_configurations WHERE id = 380;\nselect * from activities where id = 38285673; # 38285673\nSELECT * FROM users WHERE id = 16942;\nSELECT * FROM groups WHERE id = 1964;\nSELECT * FROM playbooks WHERE id = 2033;\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 499; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1678;\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\n\nSELECT * FROM activities WHERE uuid_to_bin('96b1261f-2357-49f9-ab38-23ce12008ea0') = uuid;\n\nselect * from contacts c\nwhere c.crm_configuration_id = 370 order by c.updated_at desc;\n\nSELECT * FROM participants where activity_id = 38833541;\nSELECT * FROM participants where activity_id = 39216301;\nSELECT * FROM activity_summary_logs where activity_id = 39216301;\nSELECT * FROM activities WHERE uuid_to_bin('c7d99fbe-1fb1-41f2-8f4d-52e2bf70e1e9') = uuid; # 38833541, crm 478116564181\nSELECT * FROM activities WHERE uuid_to_bin('2e6ff4d3-9faa-447a-a8c1-9acde4d885ae') = uuid; # 39216301, crm 480171536586\nselect * from crm_profiles where crm_configuration_id = 319 and crm_provider_id = 525785080;\nselect * from opportunities where crm_configuration_id = 319 and crm_provider_id = 410150124747;\nselect * from accounts where crm_configuration_id = 319 and crm_provider_id = 47150650569;\nselect * from contacts where crm_configuration_id = 319 and crm_provider_id IN ('665587441856', '742723347700');\n# owner 13236 525785080\n# contact 1 16779180 665587441856 - activity - Alex Howes alex@supportroom.com created 2026-01-26\n# contact 2 19247563 742723347700 - ash@supportroom.com 2026-03-24\n# company 4176133 47150650569\n# deal 7100953 410150124747\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 400 and sa.provider = 'hubspot';\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556; # owner: 18101, crm: 477\nselect * from crm_configurations where id = 477;\nSELECT * FROM users WHERE id = 18101;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'integration-app';\n\nselect * from opportunities where id = 7594349;\nselect * from opportunity_stages where opportunity_id = 7594349 order by created_at desc;\nselect * from business_processes where id = 6024;\nselect * from business_process_stages where stage_id = 16352;\nselect * from business_process_stages where business_process_id = 6024;\nselect * from stages where team_id = 459;\nselect * from teams where id = 459;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 459 and sa.provider = 'hubspot';\n\nSELECT os.stage_id, s.crm_provider_id, s.name, COUNT(*) as cnt\nFROM opportunity_stages os\nJOIN stages s ON s.id = os.stage_id\nWHERE os.opportunity_id = 7594349\nGROUP BY os.stage_id, s.crm_provider_id, s.name\nORDER BY cnt DESC;\n\nSELECT s.id, s.crm_provider_id, s.name, s.team_id, s.crm_configuration_id\nFROM stages s\nJOIN business_process_stages bps ON bps.stage_id = s.id\nWHERE bps.business_process_id = 6024\nAND s.crm_provider_id = 'contractsent';\n\nselect * from stages where id IN (16352,20612,18281,7344,16378,16309,5036,15223,14535,6293,12098,11607)\n\nSELECT * FROM teams WHERE name LIKE '%Pulsar Group%'; # 472, 380, 15138, raza.gilani@vuelio.com\nselect * from playbooks where team_id = 472; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 2288;\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 380;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 472 and sa.provider = 'salesforce';","depth":4,"value":"SELECT * FROM team_features where team_id = 1;\n\nSELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922\nSELECT * FROM users WHERE team_id = 340; # 12015\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 340\nand sa.provider = 'salesforce';\n# and sa.provider = 'salesloft';\n\nselect * from crm_fields where crm_configuration_id = 270 and object_type = 'event';\n# 125558 - Event Type - Event_Type__c\n# 125552 - Event Status - Event_Status__c\n\nSELECT * FROM sidekick_settings WHERE team_id = 340;\n\nSELECT * FROM crm_field_values WHERE crm_field_id in (125552);\n\nselect * from activities where crm_configuration_id = 270\nand type = 'conference' and crm_provider_id IS NOT NULL\nand actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;\n\nSELECT * FROM activities WHERE id = 20871677;\nSELECT * FROM crm_field_data WHERE activity_id = 20871677;\n\nselect * from crm_layouts where crm_configuration_id = 270;\nselect * from crm_layout_entities where crm_layout_id in (886,887);\n\nSELECT * FROM crm_configurations WHERE id = 270;\n\nselect * from playbooks where team_id = 340; # 1514\nselect * from groups where team_id = 340;\nSELECT * FROM crm_fields WHERE id IN (125393, 125401);\n\nselect g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g\njoin playbooks p on g.playbook_id = p.id\njoin crm_fields f on p.activity_field_id = f.id\nwhere g.team_id = 340;\n\nSELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716\nselect * from crm_field_data where object_id = 20448716;\n\nselect * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008\nselect * from opportunities where team_id = 343;\nselect * from opportunities where team_id = 343 and crm_provider_id = '18099102526';\nselect * from opportunities where team_id = 343 and account_id = 945217482;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from accounts where team_id = 343 order by name asc;\n\nselect * from stages where crm_configuration_id = 273 and type = 'opportunity';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143\nSELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;\nSELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';\nSELECT * FROM activities WHERE id = 20717903;\n\nselect * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 353\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, l.atkinson@mwbsolutions.co.uk\nSELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;\n# id: 20940638, user: 12022, contact: 5305871\nSELECT * FROM activity_summary_logs WHERE activity_id = 20940638;\nselect * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 345\nand sa.provider = 'hubspot';\n\nselect * from users where team_id = 345 and id = 12022;\nSELECT * FROM crm_profiles WHERE user_id = 12022;\nSELECT * FROM participants WHERE activity_id = 20940638;\nSELECT * FROM users u\nJOIN crm_profiles cp ON u.id = cp.user_id\nWHERE u.team_id = 345;\n\nselect * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871\n\nselect * from team_features where team_id = 345;\nSELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197\nSELECT * FROM participants WHERE activity_id = 20897406;\n\n\n\nSELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912\nSELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';\n\n\nSELECT * FROM activities WHERE id = 20946641;\nSELECT * FROM crm_profiles WHERE user_id = 10211;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, triger@lunio.ai\nSELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';\nselect * from stages where crm_configuration_id = 97 and type = 'opportunity';\nselect * from opportunities where team_id = 120;\n\n\nselect * from crm_configurations crm join teams t on crm.id = t.crm_id\nwhere 1=1\nAND t.current_billing_plan IS NOT NULL\nAND crm.auto_sync_activity = 0\nand crm.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,james.lewendon@exclaimer.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 270\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956\nSELECT * FROM crm_profiles WHERE user_id = 11446;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, alex.chikly@cygnetise.com\nselect * from playbooks where team_id = 372;\nselect * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340\nSELECT * FROM crm_field_values WHERE crm_field_id = 141340;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 372\nand sa.provider = 'salesforce';\n\nselect * from crm_profiles where crm_configuration_id = 300;\nSELECT * FROM crm_configurations WHERE team_id = 372;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,mfa@planday.com\nSELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756\nselect * from crm_field_data where object_id = 3207756;\nSELECT * FROM crm_fields WHERE id = 111834;\n\nselect f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value\nFROM crm_fields f\nJOIN crm_field_data fd ON f.id = fd.crm_field_id\nWHERE f.crm_configuration_id = 242\nAND f.object_type = 'opportunity'\nAND fd.object_id IN (3207756)\nORDER BY fd.object_id, fd.updated_at;\n\nSELECT * FROM crm_configurations WHERE auto_connect = 1;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,salesforce-admin@tourlane.com\nselect * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id\nwhere g.team_id = 187;\n\nselect * from `groups` where team_id = 187;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 187\nand sa.provider = 'salesforce';\n\n# Destination - 98870 - Destination__c\n# Stage - 79014 - StageName\n# Land Arrangement - 98856 - Land_Arrangement__c\n# Flight - 98848 - Flight__c\n# Last activity date - 98812 - LastActivityDate\n# Last modified date - 98809 - LastModifiedDate\n# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c\n# next call - 98864 - Next_Call__c\n\nselect * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\nselect * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';\nselect * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;\nselect * from activities where opportunity_id = 3538248;\n\nSELECT * FROM crm_profiles WHERE user_id = 8150;\n\nselect * from deal_risks where opportunity_id = 3538248;\n\nselect * from teams where crm_id IS NULL;\n\nSELECT opp.id AS opportunity_id,\n u.group_id AS group_id,\n MAX(\n CASE\n WHEN a.type IN (\"sms-inbound\", \"sms-outbound\") THEN a.created_at\n ELSE a.actual_end_time\n END) as last_date\nFROM opportunities opp\nleft join activities a on a.opportunity_id = opp.id\ninner join users u on opp.user_id = u.id\nwhere opp.user_id IN (9951)\n\nAND opp.is_closed = 0\nand a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL\ngroup by opp.id;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,polly.morphew@cybsafe.com\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 301;\nSELECT * FROM contacts WHERE id = 6612363;\nSELECT * FROM accounts WHERE id = 4235676;\nSELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;\nselect * from opportunity_stages where opportunity_id = 4503759;\n# SELECT * FROM opportunities WHERE id = 4569937;\n\nselect * from activities where crm_configuration_id = 301;\nSELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370\nSELECT * FROM participants WHERE activity_id = 26330370;\n\nSELECT * FROM teams WHERE id = 375;\nselect * from playbooks where team_id = 375;\n\nselect * from stages where crm_configuration_id = 301 and type = 'opportunity';\n\nselect * from teams;\nselect * from contact_roles;\n\nSELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';\n\nselect * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;\n\nSELECT * FROM crm_field_data WHERE object_id = 3771706;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'\nand crm_provider_id LIKE \"%traffic_light%\";\nSELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);\n\nSELECT fd.* FROM opportunities o\nJOIN crm_field_data fd ON o.id = fd.object_id\nWHERE o.team_id = 343\n# and o.user_id IS NOT NULL\nand fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)\nand fd.value != ''\norder by value desc\n# group by o.id\n;\n\nSELECT * FROM opportunities WHERE id = 3769843;\n\nSELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, salesforce-admin@tourlane.com\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 209;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,aswini.mishra@fundingcircle.com\nSELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839\n\n\nSELECT * FROM opportunities WHERE id = 3855992;\n\nSELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988\n\nSELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';\n\nselect * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507\nSELECT * FROM crm_field_data WHERE object_id = 5874411;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379\nand sa.provider = 'hubspot';\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, nikhil.kumar@mention-me.com\nSELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, salesforce-admin@tourlane.com\nSELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793\nselect * from generic_ai_prompts where subject_id = 3537793;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, triger@lunio.ai\nSELECT * FROM crm_configurations WHERE id = 97;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 97;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;\nSELECT * FROM crm_fields WHERE id = 32682;\n\nselect cfd.value, o.* from opportunities o\njoin crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682\nwhere team_id = 120\nand cfd.value != ''\n;\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 120\nand sa.provider = 'salesforce';\n\nselect * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';\nSELECT * FROM crm_field_data WHERE object_id = 2313439;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 410;\nSELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';\nselect * from scorecards where team_id = 410;\nselect * from scorecard_rules;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, aswini.mishra@fundingcircle.com\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\njoin users u on o.user_id = u.id\nwhere a.crm_configuration_id = 177 and a.type LIKE '%email-out%'\n# and a.actual_end_time > '2024-12-16 00:00:00'\n# and o.remotely_created_at > '2024-12-01 00:00:00'\n# and u.group_id = 1014\nand u.id = 9021\norder by a.id desc;\nSELECT * FROM opportunities WHERE id in (3981384,4017346);\nSELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);\n\nselect * from users where id = 9021;\nselect * from inboxes where user_id = 9021;\n\nselect * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';\n\nselect * from email_messages where team_id = 220\nand orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'\nand subject LIKE '%Personal%'\n# and 'from' = 'credit@fundingcircle.com'\n;\n\nselect * from activities a\njoin opportunities o on a.opportunity_id = o.id\nwhere a.user_id = 9021 and a.type LIKE '%email-out%'\nand a.actual_end_time > '2024-12-18 00:00:00'\nand o.user_id IS NOT NULL\nand o.remotely_created_at > '2024-12-01 00:00:00'\norder by a.id desc;\n\nSELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;\nselect * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;\n\nselect * from team_settings where name IN ('useCloseDate');\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, jfarrell@hurree.co\nSELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 104\nand sa.provider = 'hubspot';\n\nselect * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'\nselect * from teams where crm_id IS NULL;\n\nselect t.name as 'team', u.name as 'owner', u.email, u.phone\nfrom teams t\njoin activity_providers ap on t.id = ap.team_id\njoin users u on t.owner_id = u.id\nwhere 1=1\n and t.status = 'active'\n and ap.is_enabled = 1\n# and u.status = 1\n and ap.provider = 'ms-teams';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nSELECT * FROM teams WHERE id = 442; # 14293\nselect * from users where team_id = 442;\nselect * from social_accounts sa where sa.sociable_id = 14293;\nselect * from invitations where team_id = 442;\n\n# ********************************************************************************************************\nSELECT * FROM users WHERE email LIKE '%nea.liikamaa@eletive.com%'; # 14022\nSELECT * FROM teams WHERE id = 429;\nselect * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);\nselect * from activities where opportunity_id in (4340436,4353519);\n\nselect * from transcription where activity_id IN (25630961,25381771);\nselect * from generic_ai_prompts where subject_id IN (4353519);\n\nSELECT\n a.id as activity_id,\n a.opportunity_id,\n a.type as activity_type,\n a.language,\n CONCAT(a.title, a.description) AS mail_content,\n e.from AS mail_from,\n e.to AS mail_to,\n e.subject AS mail_subject,\n e.body AS mail_body,\n p.type as prompt_type,\n p.status as prompt_status,\n p.content AS prompt_content,\n a.actual_start_time as created_at\nFROM activities a\n LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL\n LEFT JOIN email_messages e ON a.id = e.activity_id\nWHERE a.actual_start_time > '2024-01-01 00:00:00'\n AND a.opportunity_id IN (4353519)\n AND a.status IN ('completed', 'received', 'delivered')\n AND a.deleted_at IS NULL\n AND a.type NOT IN ('sms-inbound', 'sms-outbound')\nORDER BY a.opportunity_id ASC, a.id ASC;\n\nSELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293\nSELECT * FROM teams WHERE id = 442;\nSELECT * FROM crm_configurations WHERE id = 344;\nselect * from team_features where team_id = 442;\nselect * from groups where team_id = 442;\nselect * from playbooks where team_id = 442;\nselect * from playbook_categories where playbook_id = 1729;\nselect * from crm_fields where crm_configuration_id = 344 and id = 172024;\nSELECT * FROM crm_field_values WHERE crm_field_id = 172024;\nselect * from crm_layouts where crm_configuration_id = 344;\nselect * from playbook_layouts where playbook_id = 1729;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444\n\nselect s.*\n# , s.sent_at, u.name, a.*\nfrom activity_summary_logs s\ninner join activities a on a.id = s.activity_id\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 356\nand s.sent_at > date_sub(now(), interval 60 day)\norder by a.actual_end_time desc;\n\nselect * from activities a\n# inner join activity_summary_logs s on s.activity_id = a.id\nwhere a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)\n# and a.crm_provider_id is not null\n# and provider <> 'ringcentral'\nand status = 'completed'\norder by a.actual_end_time desc;\n\nselect * from teams order by id desc; # 17328, 32, 17830, integration-account@jiminny.com\nSELECT * FROM users;\nSELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active\nSELECT * FROM teams WHERE id = 260;\nselect * from team_settings where team_id = 260;\nselect * from crm_configurations where team_id = 260;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 356;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;\n\nselect * from accounts where crm_configuration_id = 221 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 221 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 221 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 221 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 221;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 221 order by id desc;\nselect * from stages where crm_configuration_id = 221 order by id desc;\n\nselect * from accounts where crm_configuration_id = 356 order by id desc; # 7000\nselect * from leads where crm_configuration_id = 356 order by id desc; # 0\nselect * from contacts where crm_configuration_id = 356 order by id desc; # 200 000\nselect * from opportunities where crm_configuration_id = 356 order by id desc; # 0\nselect * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23\nselect * from crm_fields where crm_configuration_id = 356;\nselect * from crm_field_values where crm_field_id = 5302 order by id desc;\nselect * from crm_layouts where crm_configuration_id = 356 order by id desc;\nselect * from stages where crm_configuration_id = 356 order by id desc;\n\nselect * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)\nselect * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)\nselect * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4\nselect ce.* from calendars c\njoin users u on c.user_id = u.id\njoin calendar_events ce on c.id = ce.calendar_id\nwhere u.team_id = 260\nand (ce.start_time > '2025-02-21 00:00:00')\n;\n# calendar events 1207\n#\n\nselect * from opportunities where team_id = 260;\nSELECT * FROM crm_field_data WHERE object_id = 4696496;\n\nselect * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;\nselect * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')\n# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0\nand created_at > '2024-03-01 00:00:00'\norder by id desc; # 880 000, ringcentral, avaya\nSELECT * FROM participants WHERE activity_id = 26371744;\n\n# all activities 942 000 +\n# conference 7385 - scheduled 984 - external 343\n\nselect * from activities where id = 26321812;\nselect * from participants where activity_id = 26321812;\nselect * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);\nselect * from leads where id in (720428,689175,731546,645866,621037);\n\nselect * from users where id = 13841;\nselect * from opportunities where user_id = 9541;\nselect * from stages where id = 15900;\n\nselect * from accounts where\n# id IN (4160055,5053725,4965303,4896434)\nid in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)\n;\n\nselect * from activities where id = 26654935;\nSELECT * FROM opportunities WHERE id = 4803458;\n\nSELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;\nSELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time\nFROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);\n\nSELECT DISTINCT\n o.id, o.stage_id, s.name, a.title,\n a.*\nFROM activities a\n# INNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nINNER JOIN groups g ON u.group_id = g.id\nINNER JOIN opportunities o ON a.opportunity_id = o.id\nINNER JOIN stages s ON o.stage_id = s.id\nWHERE\n a.crm_configuration_id = 356\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 13841\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')\n AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')\n\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')\n )\n )\n AND (\n# s.id = 15900\n s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')\n OR s.uuid IS NULL -- Include records without opportunity stage\n )\n\nORDER BY a.actual_end_time DESC;\n# ********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, willsc@leadforensics.com\nSELECT * FROM users WHERE team_id = 190;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 190\nand sa.provider = 'hubspot';\n\nselect * from role_user where user_id = 8474;\n\nselect * from crm_configurations where provider = 'bullhorn';\n\nSELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;\nSELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;\n\nSELECT * FROM opportunities WHERE id = 4732493;\nselect * from activities where opportunity_id = 4732493;\n\n# ********************************************************************************************************\nSELECT * FROM teams WHERE id = 443; # 358, 14315, andrea.romano@correrenaturale.com\nSELECT * FROM opportunities WHERE team_id = 443;\n\nSELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id\nFROM activities AS a\nJOIN stages AS s ON a.stage_id = s.id\nJOIN users AS u ON u.id = a.user_id\nJOIN teams AS t ON t.id = s.team_id\nWHERE u.team_id <> s.team_id and t.id > 135;\n\n\nSELECT\n crm_configuration_id,\n crm_provider_id,\n COUNT(*) as duplicate_count,\n GROUP_CONCAT(id) as stage_ids,\n GROUP_CONCAT(name) as stage_names\nFROM stages\nGROUP BY crm_configuration_id, crm_provider_id\nHAVING COUNT(*) > 1\nORDER BY duplicate_count DESC;\n\nselect * from stages where id IN (14898,14907);\n\nselect * from business_processes;\n\nSELECT *\nFROM crm_configurations\nWHERE team_id IN (\n SELECT team_id\n FROM crm_configurations\n GROUP BY team_id\n HAVING COUNT(*) > 1\n)\nORDER BY team_id;\n\nSELECT *\nFROM teams\nWHERE crm_id IN (\n SELECT crm_id\n FROM teams\n GROUP BY crm_id\n HAVING COUNT(*) > 1\n)\nORDER BY crm_id;\n\n# ***************************************************************************\nselect * from crm_configurations where provider = 'integration-app';\nSELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 andrea.romano@correrenaturale.com\nselect * from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;\nselect * from team_features where team_id = 358;\nselect * from activity_summary_logs;\n\nselect * from teams where id = 406;\n\n# ************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, srv.salesforce@sportfive.com\nselect * from activities where crm_configuration_id = 202 order by actual_end_time desc;\n\nSELECT * FROM users where id = 14637;\nSELECT * FROM teams where id = 267;\nSELECT * FROM groups where id = 1118;\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 202\n AND status IN ('completed', 'failed')\n AND recording_state != 'stopped'\n AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n AND (is_private = 0 OR user_id = 14637)\n AND (\n (\n actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n ) OR (\n actual_start_time IS NULL\n AND type IN ('sms-outbound', 'sms-inbound')\n AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND NOT EXISTS (\n SELECT 1\n FROM tracks\n WHERE\n tracks.activity_id = activities.id\n AND tracks.type IN ('audio', 'video')\n )\nORDER BY actual_end_time DESC;\n\nSELECT DISTINCT\n a.*\nFROM activities a\nINNER JOIN tracks t ON a.id = t.activity_id\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams team ON u.team_id = team.id\nWHERE\n a.crm_configuration_id = 202\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n# and a.user_id = 14637\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND t.type IN ('audio', 'video')\n AND (\n (a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')\n OR\n (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'\n )\n )\n AND (\n a.is_private = 0\n OR (\n a.is_private = 1\n AND a.user_id = 14637\n )\n )\n\nORDER BY a.actual_end_time DESC\n;\n\nSELECT DISTINCT a.*\nFROM activities a\nINNER JOIN users u ON a.user_id = u.id\nINNER JOIN teams t ON u.team_id = t.id\n# INNER JOIN tracks tr ON a.id = tr.activity_id\n# INNER JOIN groups g ON u.group_id = g.id\nWHERE 1=1\n AND t.id = 267\n# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')\n AND a.status IN ('completed', 'failed')\n AND a.recording_state != 'stopped'\n AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n# AND tr.type NOT IN ('audio', 'video')\n AND (\n a.is_private = 0\n OR a.user_id = 14637\n )\n AND (\n (a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')\n OR (\n a.actual_start_time IS NULL\n AND a.type IN ('sms-outbound', 'sms-inbound')\n AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'\n )\n )\n# and NOT EXISTS (\n# SELECT 1\n# FROM tracks t\n# WHERE t.activity_id = a.id\n# AND t.type IN ('audio', 'video')\n# )\n\nORDER BY a.actual_end_time DESC;\n\nSELECT * FROM tracks WHERE activity_id = 26485995;\n\nselect a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\nwhere a.crm_configuration_id = 202\n# and a.is_internal = 0\nand (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type IN (\"softphone\",\"softphone-inbound\",\"conference\",\"sms-inbound\")\nand a.status IN ('completed', 'failed')\n# and a.external_id is not null\norder by a.actual_end_time desc;\n\nselect * from activities a where a.crm_configuration_id = 202\nand a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'\n# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')\n\nselect g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a\ninner join users u on u.id = a.user_id\ninner join groups g on g.id = u.group_id\nwhere a.crm_configuration_id = 202\nand a.is_internal = 0\nand (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')\nand a.type = 'conference'\nand a.status != 'completed'\nand a.external_id is not null\norder by a.scheduled_start_time desc;\n\nSELECT * FROM teams WHERE name LIKE '%Tourlane%';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';\nSELECT * FROM crm_field_data WHERE crm_field_id = 98809;\n\nselect * from users where status = 1 AND timezone = 'MDT';\n\nselect * from opportunities where id = 3769814;\nselect * from deal_risks where opportunity_id = 3769814;\n\nselect cp.* from crm_profiles cp\njoin users u on cp.user_id = u.id\njoin crm_configurations crm on cp.crm_configuration_id = crm.id\nwhere crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';\n\nselect * from crm_fields where id = 154575;\n\nselect * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';\nSELECT * FROM teams WHERE id = 176; # crm 148\nselect * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nselect * from crm_fields cf\njoin crm_configurations crm on crm.id = cf.crm_configuration_id\nwhere crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');\n\n# *********************************************************************************************\nSELECT * FROM users WHERE id IN (15415, 15418);\nSELECT * FROM groups WHERE id IN (1805,1806);\nSELECT * FROM playbooks WHERE id = 1860;\nSELECT * FROM playbook_categories WHERE id = 38634;\nSELECT * FROM crm_fields WHERE id = 189962;\n\nSELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 raza.gilani@vuelio.com\n\nSELECT * FROM crm_profiles WHERE user_id = 15415;\nSELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';\n\nselect * from sidekick_settings where team_id = 472;\n\nSELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418\nSELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415\nSELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415\n\n# *********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, salesforce-integrations@teamtailor.com\nselect * from crm_configurations where id = 218;\nSELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765\nSELECT * FROM users WHERE id IN (13232, 13230);\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n0057R00000EPL5HQAX Inez Ekblad\n\n1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur\n\nSELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);\n\n############################################################################################\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id IN (94491,94493,94498);\nSELECT * FROM users WHERE id = 13658;\nSELECT * FROM teams WHERE id = 109;\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, katy.holden@strengthscope.comk\nSELECT * FROM stages WHERE crm_configuration_id = 390;\nselect * from business_processes where team_id = 481 and crm_configuration_id = 390;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 481\nand sa.provider = 'salesforce';\n\n\nSELECT * FROM users WHERE id = 15780; # team 462\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 462\nand sa.provider = 'hubspot';\n\n\nselect * from teams where id = 495;\nSELECT * FROM users WHERE id = 15794;\nselect * from social_accounts where sociable_id = 15794;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752\nSELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794\nSELECT * FROM activities WHERE crm_configuration_id = 407\nand status = 'completed' and type = 'conference'\norder by id desc;\n\nselect ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id\njoin permission_role pr on pr.role_id = ru.role_id\n join permissions p on p.id = pr.permission_id\nwhere team_id = 495 and p.name IN ('dial');\n\nselect * from permission_role;\n\nselect * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;\nSELECT * FROM activities WHERE id = 29512773;\nSELECT * FROM activities WHERE id IN (29042721,28991325,29002874);\n\nSELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 407\n# and a.id IN (29042721,28991325,29002874);\n\nSELECT * FROM users WHERE id = 15794;\nSELECT * FROM users WHERE team_id = 495;\nSELECT * FROM social_accounts WHERE sociable_id = 15794;\nSELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';\nSELECT * FROM contacts WHERE team_id = 495;\nSELECT * FROM leads WHERE team_id = 495;\nSELECT * FROM accounts WHERE team_id = 495;\nSELECT * FROM crm_profiles WHERE crm_configuration_id = 407;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 407;\nSELECT * FROM crm_configurations WHERE id = 407;\nSELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'\nand user_id IS NOT NULL and is_closed = 1 and is_won = 1;\n\n# ********************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103\nSELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064\nSELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');\n\n# *********************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 325\nand sa.provider = 'hubspot';\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085\nSELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733\nSELECT * FROM activity_summary_logs where activity_id = 28719733;\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444\nSELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';\nSELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630\nselect * from activities where crm_configuration_id = 356 and lead_id = 841732;\n\nSELECT * from activity_summary_logs al join activities a on a.id = al.activity_id\nwhere a.crm_configuration_id = 356;\n\nselect * from activities where crm_configuration_id = 356\nand actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'\norder by id desc;\n\nselect * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;\nselect * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\nselect * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;\n\nselect * from team_features where team_id = 260;\nselect * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);\n\nSELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;\n\nselect * from crm_fields;\nselect * from crm_layout_entities;\n\nSELECT * FROM teams WHERE name LIKE '%Optable%';\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969\nSELECT * FROM crm_configurations WHERE id = 218;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 109\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939\nSELECT * FROM crm_field_data WHERE activity_id = 28655939;\nSELECT * FROM crm_fields WHERE id in (94491,94493,94498);\n\nselect * from teams where crm_id IS NULL;\n\nSELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;\n\n# *************************************************************************************************\nselect * from team_domains where team_id = 399;\nSELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207\n\nselect * from calendar_events where id = 5163781;\nSELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896\nSELECT * FROM participants WHERE activity_id = 29443896;\nselect * from contacts where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\nselect * from leads where crm_configuration_id = 318 and email = 'marianne.westeng@strawberry.no';\n\nselect * from activities where user_id = 14937 order by created_at ;\n\nselect * from users where id = 14937;\n\nselect * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';\nselect * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';\n\nselect * from activities a join participants p on a.id = p.activity_id\nwhere crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';\n\n# *************************************************************************************************\nSELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';\nSELECT * FROM opportunities WHERE team_id = 379 order by id desc;\nSELECT * FROM teams WHERE id = 379;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 379 and sociable_id = 13852\nand sa.provider = 'hubspot';\n\nSELECT * FROM crm_configurations WHERE id = 307;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 307;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 307\n and id IN (144750,144855,145158,155227);\n\nSELECT * FROM activities;\n\n\nselect * from activities\nwhere created_at > '2025-07-01 00:00:00'\n# and created_at < '2025-08-01 00:00:00'\nand type not in ('email-outbound', 'email-inbound')\nand account_id is null\nand contact_id is null\nand lead_id is null\nand opportunity_id is not null\n;\nSELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);\nSELECT * FROM crm_configurations WHERE id in (335,301,200);\n\nselect * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';\n\nSELECT * FROM teams WHERE name LIKE '%Resights%';\nselect * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';\n\nselect * from crm_configurations where provider = 'bullhorn'; # 344\nselect * from teams where id IN (442);\n\nselect * from activities\nwhere crm_configuration_id = 177\nand provider = 'amazon-connect'\n order by id desc;\n# and source <> 'gong';\n\nselect * from activity_providers where provider = 'amazon-connect';\n\nSELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;\n\n\nselect * from crm_configurations where store_transcript = 1;\nSELECT * FROM teams WHERE id IN (80);\n\n# *************************************************************************************************\nSELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 277\nand sa.provider = 'salesforce';\n\nselect * from activities where crm_configuration_id = 213 and account_id = 2511502;\n\nselect * from crm_configurations where id = 213;\n\nSELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604\nSELECT * FROM participants WHERE activity_id = 33981604;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';\n\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 431\nand sa.provider = 'salesforce';\nSELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223\nselect * from activity_summary_logs where activity_id = 33997223;\nselect * from activity_notes where activity_id = 33997223;\n\n# ***********************************\nSELECT * FROM teams WHERE name LIKE '%Abode%';\n\n\nselect * from features;\nselect * from teams t\nwhere t.status = 'active'\nand id NOT IN (select team_id from team_features where feature_id = 9)\n;\n\n\nselect * from playbook_layouts where playbook_id = 1725;\nSELECT * FROM activities WHERE uuid_to_bin('65cc283c-4849-49e6-927f-4c281c8fea19') = uuid; # 34297473\nselect * from teams where id = 318;\nselect * from crm_configurations where team_id = 318;\nselect * from playbooks where team_id = 318;\nSELECT * FROM crm_layouts where crm_configuration_id = 381;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1259;\nSELECT * FROM crm_fields WHERE id IN (192938,192936,192939);\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1266;\nSELECT * FROM crm_fields WHERE id IN (192980,192991,192997,192998,193064,193067);\n\nSELECT * FROM activities WHERE uuid_to_bin('a902289b-285c-48eb-9cc2-6ad6c5d938f5') = uuid; # 34297533\n\n\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nSELECT * FROM crm_fields WHERE id IN (131668,131669,131670,131671,131676,131797);\n\nSELECT * FROM teams WHERE name LIKE '%Peripass%'; # 351, 281, 12124\nselect * from crm_layouts where crm_configuration_id = 281;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 927;\nselect * from crm_fields where crm_configuration_id = 281 and id in (131668,131669,131670,131671,131676,131797);\nselect * from opportunities where crm_configuration_id = 281;\n\nSELECT * FROM activities WHERE id IN (34211315, 34130075);\nSELECT * FROM crm_field_data WHERE object_id IN (34211315, 34130075);\n\nselect cf.crm_configuration_id, cle.crm_layout_id, cle.id, cf.id from crm_field_data cfd\njoin crm_layout_entities cle on cle.id = cfd.crm_layout_entity_id\njoin crm_fields cf on cle.crm_field_id = cf.id\nwhere cf.deleted_at IS NOT NULL\nGROUP BY cle.id, cf.id;\n\nselect * from crm_layouts where id IN (355);\nselect u.email, t.crm_id, t.* from teams t\njoin users u on u.id = t.owner_id\nwhere crm_id IN (97);\n\nSELECT * FROM crm_fields WHERE id = 96492;\n\nselect * from permissions;\nselect * from permission_role where permission_id = 247;\nselect * from roles;\n\nselect * from migrations;\n# *****************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('291e3c21-11cc-4728-aee7-6e4bedf86d72') = uuid; # 34262174\nSELECT * FROM crm_configurations WHERE id = 301;\nSELECT * FROM teams WHERE id = 343;\nselect * from social_accounts sa\njoin users u on sa.sociable_id = u.id\nwhere u.team_id = 343\nand sa.provider = 'hubspot';\n\nselect * from participants where activity_id = 34262174;\n\nselect * from contacts where crm_configuration_id = 301 and id = 6976326;\nselect * from accounts where crm_configuration_id = 301 and id IN (4647626, 4815829); # 30761335403\n\nselect * from activity_summary_logs where activity_id = 34262174;\n\nselect * from users where status = 1 AND timezone = 'EST';\n\n# ****************************************************************************\nSELECT * FROM users WHERE id = 13869;\nSELECT * FROM crm_configurations WHERE id = 320;\nSELECT * FROM teams WHERE id = 401;\n\nSELECT * FROM activities WHERE uuid_to_bin('2228c16f-10be-48d5-90d4-67385219dc01') = uuid; # 29670601\n\nSELECT * FROM accounts WHERE id = 7761483;\nSELECT * FROM opportunities WHERE id = 6051814;\n\nSELECT * FROM teams WHERE name LIKE '%Seedlegals%';\n\n;select * from opportunities where updated_at > '2025-10-11' AND crm_provider_id = '34713761166';\n\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 177;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 577;\nSELECT * FROM crm_fields WHERE id IN (68458,68459,68480,68497,68524,68530,68554,68618,68662,68781,68810,68898,68981,69049,97467);\n\nSELECT t.id, crm.id, t.name, crm.sync_objects, crm.provider, crm.last_synced_at FROM crm_configurations crm join teams t on t.crm_id = crm.id\nwhere t.status = 'active' AND crm.provider = 'hubspot' AND crm.last_synced_at < '2025-10-22 00:00:00';\n\nSELECT * FROM activities WHERE uuid_to_bin('fa09449f-cba9-496a-b8f3-865cd3c72351') = uuid;\nSELECT * FROM crm_configurations where id = 184;\nSELECT * FROM teams WHERE id = 246;\nSELECT * FROM social_accounts WHERE sociable_id = 9259 and provider = 'hubspot';\n\nSELECT * FROM users WHERE email LIKE '%rhian.old@bud.co.uk%'; # 17700\nSELECT * FROM teams WHERE id = 551;\n\nSELECT * FROM crm_configurations WHERE id = 471;\nSELECT * FROM activities WHERE crm_configuration_id = 471 and crm_provider_id IS NOT NULL;\nSELECT * FROM crm_fields WHERE crm_configuration_id = 471;\nSELECT * FROM crm_fields WHERE id = 307260;\nSELECT * FROM crm_field_values WHERE crm_field_id = 307260;\n\nselect * from crm_layouts where crm_configuration_id = 471;\n\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1547;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1548;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 551 and sa.provider = 'hubspot';\n\nSELECT * FROM teams WHERE name LIKE '%$PCS%';\n\n# ********************************************************************************************************\nselect * from crm_configurations crm\njoin teams t on t.crm_id = crm.id\nwhere t.status = 'active'\nand crm.provider = 'hubspot';\n\n# $slug = 'HUBSPOT_WEBHOOK_SYNC';\n# $team = Jiminny\\Models\\Team::find(2);\n# $feature = Feature::query()->where('slug', $slug)->first();\n# TeamFeature::query()->create(['feature_id' => $feature->getId(),'team_id' => $team->getId()]);\n\n# hubspot_webhook_metrics\n\nselect * from crm_configurations where id = 331; # 416\nSELECT * FROM teams WHERE id = 416;\nSELECT * FROM opportunities WHERE team_id = 190;\n\nSELECT * FROM teams WHERE name LIKE '%Lead Forensics%';\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 190 and sa.provider = 'hubspot';\n\n\n\nSELECT * FROM teams WHERE name LIKE '%Rapaport%'; # 431, 337\nSELECT * FROM teams where id = 431;\nSELECT * FROM crm_configurations where team_id = 431;\nSELECT * FROM activity_providers where team_id = 431;\nSELECT * FROM activities where crm_configuration_id = 337 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 431 and sa.provider = 'salesforce';\n\nSELECT * FROM teams WHERE name LIKE '%BiP%'; # 401, 320\nSELECT * FROM teams where id = 401;\nSELECT * FROM crm_configurations where team_id = 401;\nSELECT * FROM activity_providers where team_id = 401;\nSELECT * FROM activities where crm_configuration_id = 320 and type IN ('softphone', 'softphone-outbound')\nand provider NOT IN ('hubspot', 'aircall')\n# and telephony_provider_id = '019c1131-a22f-4792-b9ea-20adf6a02ed0'\norder by id desc;\nSELECT sa.id,\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 401 and sa.provider = 'salesforce';\n\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 307; # 379 - Story Terrace Inc , portalId: 3921157\nSELECT * FROM contacts WHERE team_id = 379 and updated_at > '2026-01-31 11:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-01 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 379 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 379 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 485; # 563 - LATUS Group (ad94d501-5d09-44fd-878f-ca3a9f8865c3) , portalId: 3904501\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 563 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 338; # 432 - Formalize , portalId: 9214205\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 432 and sa.provider = 'hubspot';\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 432 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 436; # 519 - Moxso , portalId: 25531989\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 519 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 96; # 119 - Nourish Care , portalId: 26617984\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-02 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 119 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 331; # 416 - The National College , portalId: 7213852\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 416 and updated_at > '2026-02-04 11:00:00' order by updated_at desc;\n\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 308; # 380 - Foodles , portalId: 7723616\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 380 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 379; # 471 - imat-uve , portalId: 9177354\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 471 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 465; # 545 - Spotler , portalId: 144759271\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 545 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 455; # 537 - indevis , portalId: 25666868\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 537 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 200; # 265 - Jobadder , portalId: 6426676\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 265 and updated_at > '2026-02-06 10:30:00' order by updated_at desc;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 335; # 429 - Eletive , portalId: 6110563\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 429 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 363; # 456 - Global Group , portalId: 8901981\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 456 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 297; # 369 - Unbiased , portalId: 9229005\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 369 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 353; # 449 - Fuuse , portalId: 25781745\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 449 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 487; # 566 - Nimbus , portalId: 39982590\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-04 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 566 and updated_at > '2026-02-09 10:30:00' order by updated_at desc;\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 487;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1630;\nselect * from crm_fields where crm_configuration_id = 487 and\n(uuid_to_bin('4c6b2971-64d4-45b8-b377-427be758b5a5') = uuid or uuid_to_bin('59e368d8-65a0-4b77-b611-db37c99fbe68') = uuid);\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM crm_configurations where id = 420; # 506 - voiio , portalId: 145629154\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 506 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 479; # 558 - Momice , portalId: 535962\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 558 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 59; # 80 - Storyclash GmbH , portalId: 4268479\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 80 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 175; # 203 - Team iAM , portalId: 5534732\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 203 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\nSELECT * FROM crm_configurations where id = 368; # 460 - OneTouch Health , portalId: 5534732183355\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 00:00:00' order by updated_at desc;\nSELECT * FROM opportunities WHERE team_id = 460 and updated_at > '2026-02-10 15:00:00' order by updated_at desc;\n\n\n\nselect * from users where id = 29643;\nSELECT * FROM crm_field_values WHERE crm_field_id = 375177;\n# ********************************************************************\nSELECT * FROM teams WHERE name LIKE '%Buynomics%'; # 462, 482, 14910\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\n# and description like '%The call focused on understanding Welch%'\norder by id desc;\n\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 462 and sa.provider = 'salesforce';\n\nselect * from contacts where crm_configuration_id = 482 and name = 'Cyndall Hill'; # 15504749\nselect * from contacts where id = 10891096; # 482\nSELECT * FROM activities WHERE crm_configuration_id = 482\nand type NOT IN ('email-inbound', 'email-outbound')\nand contact_id = 15504749\norder by id desc;\n\nselect * from activities where id = 36793003; # 96cc7bc1-8622-4d27-92f4-baf664fc1a56, 00UOf00000PDdOXMA1\nselect * from transcription where id = 7646782;\nselect * from ai_prompts where transcription_id = 7646782;\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7a8471a3-847e-4822-802b-ddf426bbc252') = uuid; # 37370018\nSELECT * FROM activity_summary_logs WHERE activity_id = 37370018;\nSELECT * FROM teams WHERE id = 555;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 555 and sa.provider = 'hubspot';\n\n# ********************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('7c17b8aa-09df-4f85-a0f7-51f47afd712d') = uuid; # 37395250\nSELECT * FROM activities WHERE uuid_to_bin('14d60388-260d-494b-aa0d-63fdb1c78026') = uuid; # 37395250\n\nSELECT a.* FROM activities a JOIN crm_configurations c on c.id = a.crm_configuration_id\nwhere a.type IN ('softphone', 'softphone-outbound') and c.provider = 'hubspot'\nand a.provider NOT IN ('hubspot')\n# and a.provider IN ('salesloft')\n# and c.id NOT IN (70)\n# and a.duration > 30\n# and actual_start_time > '2026-02-05 00:00:00'\norder by a.id desc;\n\nSELECT * FROM activities WHERE id = 37549787;\nSELECT * FROM crm_profiles WHERE user_id = 17613;\n\nSELECT * FROM crm_configurations WHERE id = 70;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 93 and sa.provider = 'hubspot';\n\nSELECT asf.activity_search_id, asf.id, asf.value\nFROM activity_search_filters asf\nWHERE asf.filter = 'group_id'\nAND asf.value IN (\n SELECT CONCAT(\n HEX(SUBSTR(uuid, 5, 4)), '-',\n HEX(SUBSTR(uuid, 3, 2)), '-',\n HEX(SUBSTR(uuid, 1, 2)), '-',\n HEX(SUBSTR(uuid, 9, 2)), '-',\n HEX(SUBSTR(uuid, 11))\n )\n FROM groups\n WHERE deleted_at IS NOT NULL\n);\n\nSELECT * FROM crm_configurations WHERE id = 373; # KPSBremen.de 465 # - no social account\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 465 and sa.provider = 'hubspot';\n\nselect * from crm_configurations where id = 494;\n\nSELECT * FROM teams WHERE name LIKE '%splose%'; # 572, 495, 18708\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 572 and sa.provider = 'pipedrive';\n\nselect * from opportunities where team_id = 572\n# and name like '%Onebright%'\n# and is_closed = 1 and is_won = 0\n order by id desc;\n\n\nselect * from users where deleted_at is null and status = 2;\n\nselect * from contacts where id = 17900517;\nselect * from accounts where id = 10109838;\nselect * from opportunities where id = 6955880;\n\nselect * from opportunity_contacts where opportunity_id = 6955880;\nselect * from opportunity_contacts where contact_id = 17900517;\n\nselect * from contact_roles cr join crm_configurations crm on cr.crm_configuration_id = crm.id\nwhere crm.provider != 'salesforce';\n\nSELECT * FROM activities WHERE uuid_to_bin('adcb8331-5988-4353-834e-383a355abba2') = uuid; # 38056424, crm 104659682404\nselect * from teams where id = 456;\nSELECT * FROM crm_configurations WHERE id = 363;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 456 and sa.provider = 'hubspot';\n\nselect * from crm_layouts where crm_configuration_id = 363;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id IN (1203, 1204, 1635);\nSELECT * FROM crm_fields WHERE id IN (181536, 181538, 213455);\n\nSELECT * FROM teams WHERE name LIKE '%Electric%'; # 342, 272, 12767\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and name like 'NORTHUMBRIA POL%'; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 order by remotely_created_at asc; # and updated_at > '2025-07-01 00:00:00';\nSELECT * FROM opportunities WHERE crm_configuration_id = 272 and updated_at > '2026-01-01 00:00:00';\nSELECT * FROM crm_fields WHERE crm_configuration_id = 272 and object_type = 'opportunity';\nSELECT * FROM crm_field_values WHERE crm_field_id = 127164;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 342 and sa.provider = 'pipedrive';\n\nSELECT * FROM teams WHERE id = 472;\nSELECT * FROM crm_configurations WHERE id = 380;\nselect * from activities where id = 38285673; # 38285673\nSELECT * FROM users WHERE id = 16942;\nSELECT * FROM groups WHERE id = 1964;\nSELECT * FROM playbooks WHERE id = 2033;\n\nselect * from teams where created_at > '2026-03-09';\nSELECT * FROM crm_layouts WHERE crm_configuration_id = 499; # 1065\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 1678;\n\nSELECT * FROM teams WHERE id = 575;\nselect * from opportunities where team_id = 575;\n\nSELECT * FROM activities WHERE uuid_to_bin('96b1261f-2357-49f9-ab38-23ce12008ea0') = uuid;\n\nselect * from contacts c\nwhere c.crm_configuration_id = 370 order by c.updated_at desc;\n\nSELECT * FROM participants where activity_id = 38833541;\nSELECT * FROM participants where activity_id = 39216301;\nSELECT * FROM activity_summary_logs where activity_id = 39216301;\nSELECT * FROM activities WHERE uuid_to_bin('c7d99fbe-1fb1-41f2-8f4d-52e2bf70e1e9') = uuid; # 38833541, crm 478116564181\nSELECT * FROM activities WHERE uuid_to_bin('2e6ff4d3-9faa-447a-a8c1-9acde4d885ae') = uuid; # 39216301, crm 480171536586\nselect * from crm_profiles where crm_configuration_id = 319 and crm_provider_id = 525785080;\nselect * from opportunities where crm_configuration_id = 319 and crm_provider_id = 410150124747;\nselect * from accounts where crm_configuration_id = 319 and crm_provider_id = 47150650569;\nselect * from contacts where crm_configuration_id = 319 and crm_provider_id IN ('665587441856', '742723347700');\n# owner 13236 525785080\n# contact 1 16779180 665587441856 - activity - Alex Howes alex@supportroom.com created 2026-01-26\n# contact 2 19247563 742723347700 - ash@supportroom.com 2026-03-24\n# company 4176133 47150650569\n# deal 7100953 410150124747\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 400 and sa.provider = 'hubspot';\n\nselect * from features;\nselect * from team_features where feature_id = 40;\n\nselect * from teams where id = 556; # owner: 18101, crm: 477\nselect * from crm_configurations where id = 477;\nSELECT * FROM users WHERE id = 18101;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 556 and sa.provider = 'integration-app';\n\nselect * from opportunities where id = 7594349;\nselect * from opportunity_stages where opportunity_id = 7594349 order by created_at desc;\nselect * from business_processes where id = 6024;\nselect * from business_process_stages where stage_id = 16352;\nselect * from business_process_stages where business_process_id = 6024;\nselect * from stages where team_id = 459;\nselect * from teams where id = 459;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 459 and sa.provider = 'hubspot';\n\nSELECT os.stage_id, s.crm_provider_id, s.name, COUNT(*) as cnt\nFROM opportunity_stages os\nJOIN stages s ON s.id = os.stage_id\nWHERE os.opportunity_id = 7594349\nGROUP BY os.stage_id, s.crm_provider_id, s.name\nORDER BY cnt DESC;\n\nSELECT s.id, s.crm_provider_id, s.name, s.team_id, s.crm_configuration_id\nFROM stages s\nJOIN business_process_stages bps ON bps.stage_id = s.id\nWHERE bps.business_process_id = 6024\nAND s.crm_provider_id = 'contractsent';\n\nselect * from stages where id IN (16352,20612,18281,7344,16378,16309,5036,15223,14535,6293,12098,11607)\n\nSELECT * FROM teams WHERE name LIKE '%Pulsar Group%'; # 472, 380, 15138, raza.gilani@vuelio.com\nselect * from playbooks where team_id = 472; # event 226147\nSELECT * FROM playbook_categories WHERE playbook_id = 2288;\nSELECT * FROM crm_fields WHERE id = 226147;\nSELECT * FROM crm_field_values WHERE crm_field_id = 226147;\n\nSELECT * FROM crm_configurations WHERE id = 380;\nSELECT\n CONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '' END) AS user_id,\n u.email,\n sa.*,\n t.owner_id FROM social_accounts sa\nJOIN users u on u.id = sa.sociable_id\nJOIN teams t on t.id = u.team_id\nWHERE u.team_id = 472 and sa.provider = 'salesforce';","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7558096554454067353
|
2146738311620114029
|
click
|
accessibility
|
NULL
|
Tests passed: 7
text/html
text/html
text/html
Proj Tests passed: 7
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
TrackAutomatedReportGeneratedEventTest
Run 'TrackAutomatedReportGeneratedEventTest'
Debug 'TrackAutomatedReportGeneratedEventTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
2
1
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Listeners\AutomatedReports\UserPilot;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Events\AutomatedReports\AutomatedReportGenerated;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\UserPilot\UserPilotClient;
class TrackAutomatedReportGeneratedEvent implements ShouldQueue
{
use InteractsWithQueue;
private const string EVENT_NAME_AUTOMATED_REPORT = 'automated-report-generated';
private const string EVENT_NAME_ASK_JIMINNY_REPORT = 'ask-jiminny-report-generated';
public string $queue = Constants::QUEUE_DELAYABLE;
public function __construct(
private readonly UserPilotClient $userPilotClient,
private readonly AutomatedReportsService $automatedReportsService,
) {
}
public function handle(AutomatedReportGenerated $event): void
{
if (config('services.userpilot.token') === null) {
return;
}
$automatedReport = $event->automatedReport;
$payload = $this->buildPayload($automatedReport);
$eventName = $this->resolveEventName($automatedReport);
try {
foreach ($this->resolveUsers($automatedReport) as $user) {
$this->userPilotClient->track($user, $eventName, $payload);
}
} catch (GuzzleException $e) {
$this->release(3600);
}
}
/**
* @return array<UserContract>
*/
private function resolveUsers(AutomatedReport $automatedReport): array
{
if ($automatedReport->isAskJiminnyReport()) {
$creator = $automatedReport->getCreator();
return $creator !== null ? [$creator] : [];
}
return $this->automatedReportsService->getRecipientUserObjects($automatedReport);
}
private function buildPayload(AutomatedReport $automatedReport): array
{
return [
'report_type' => $automatedReport->getType(),
'frequency' => $automatedReport->getFrequency(),
];
}
private function resolveEventName(AutomatedReport $automatedReport): string
{
if ($automatedReport->isAskJiminnyReport()) {
return self::EVENT_NAME_ASK_JIMINNY_REPORT;
}
return self::EVENT_NAME_AUTOMATED_REPORT;
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
27
9
23
3
105
Previous Highlighted Error
Next Highlighted Error
SELECT * FROM team_features where team_id = 1;
SELECT * FROM teams WHERE name LIKE '%Vixio%'; # 340,270,11922
SELECT * FROM users WHERE team_id = 340; # 12015
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 340
and sa.provider = 'salesforce';
# and sa.provider = 'salesloft';
select * from crm_fields where crm_configuration_id = 270 and object_type = 'event';
# 125558 - Event Type - Event_Type__c
# 125552 - Event Status - Event_Status__c
SELECT * FROM sidekick_settings WHERE team_id = 340;
SELECT * FROM crm_field_values WHERE crm_field_id in (125552);
select * from activities where crm_configuration_id = 270
and type = 'conference' and crm_provider_id IS NOT NULL
and actual_start_time > '2024-09-16 09:00:00' order by scheduled_start_time;
SELECT * FROM activities WHERE id = 20871677;
SELECT * FROM crm_field_data WHERE activity_id = 20871677;
select * from crm_layouts where crm_configuration_id = 270;
select * from crm_layout_entities where crm_layout_id in (886,887);
SELECT * FROM crm_configurations WHERE id = 270;
select * from playbooks where team_id = 340; # 1514
select * from groups where team_id = 340;
SELECT * FROM crm_fields WHERE id IN (125393, 125401);
select g.name as 'team name', p.name as 'playbook name', f.label as 'activity type field' from groups g
join playbooks p on g.playbook_id = p.id
join crm_fields f on p.activity_field_id = f.id
where g.team_id = 340;
SELECT * FROM activities WHERE uuid_to_bin('0c180357-67d2-419e-a8c3-b832a3490770') = uuid; # 20448716
select * from crm_field_data where object_id = 20448716;
select * from activities where crm_configuration_id = 270 and provider = 'salesloft' order by id desc;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%CybSafe%'; # 343,273,12008
select * from opportunities where team_id = 343;
select * from opportunities where team_id = 343 and crm_provider_id = '18099102526';
select * from opportunities where team_id = 343 and account_id = 945217482;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
select * from accounts where team_id = 343 order by name asc;
select * from stages where crm_configuration_id = 273 and type = 'opportunity';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Voyado%'; # 353,283,12143
SELECT * FROM activities WHERE crm_configuration_id = 283 and account_id = 3777844 order by id desc;
SELECT * FROM accounts WHERE team_id = 353 AND name LIKE '%Salesloft%';
SELECT * FROM activities WHERE id = 20717903;
select * from participants where activity_id IN (20929172,20928605,20928468,20926272,20926271,20926270,20926269,20916499,20916454,20916436,20916435,20900015,20900014,20900013,20897312,20897243,20897241,20897237,20897232,20897229,20893648,20893231,20893230,20893229,20893228,20889784,20885039,20885038,20885037,20885036,20885035,20882728,20882708,20882703,20882702,20869828,20869811,20869806,20869801,20869799,20869798,20869796,20869795,20869794,20869761,20869760,20869759,20868688,20868687,20850340,20847195,20841710,20833967,20827021,20825307,20825305,20825297,20824615,20824400,20823927,20821760,20795588,20794233,20794057,20793710,20785811,20781789,20781394,20781307,20762651,20758453,20758282,20757323,20756643,20756636,20756629,20756627,20756606,20756605,20756604,20756603,20756602,20756600,20756599,20756598,20756595,20756594,20756589,20756587,20756577,20756573,20748918,20748386,20748385,20748384,20748383,20748382,20748381,20748380,20748379,20748377,20748375,20748373,20743301,20717905,20717904,20717903,20717901,20717899);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 353
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%modern world business solutions%'; # 345,275,12016, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('3921d399-3fef-4609-a291-b0097a166d43') = uuid;
# id: 20940638, user: 12022, contact: 5305871
SELECT * FROM activity_summary_logs WHERE activity_id = 20940638;
select * from contacts where team_id = 345 and crm_provider_id = '30891432415' order by name asc; # 5305871
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 345
and sa.provider = 'hubspot';
select * from users where team_id = 345 and id = 12022;
SELECT * FROM crm_profiles WHERE user_id = 12022;
SELECT * FROM participants WHERE activity_id = 20940638;
SELECT * FROM users u
JOIN crm_profiles cp ON u.id = cp.user_id
WHERE u.team_id = 345;
select * from contacts where team_id = 345 and crm_provider_id = '30880813535' order by name desc; # 5305871
select * from team_features where team_id = 345;
SELECT * FROM activities WHERE uuid_to_bin('11701e2d-2f82-4dab-a616-1db4fad238df') = uuid; # 21115197
SELECT * FROM participants WHERE activity_id = 20897406;
SELECT * FROM activities WHERE uuid_to_bin('63ba55cd-1abc-447d-83da-0137000005b7') = uuid; # 20953912
SELECT * FROM activities WHERE crm_configuration_id = 275 and provider = 'ringcentral' and title like '%1252629100%';
SELECT * FROM activities WHERE id = 20946641;
SELECT * FROM crm_profiles WHERE user_id = 10211;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120,97,10984, [EMAIL]
SELECT * FROM opportunities WHERE crm_configuration_id = 97 and crm_provider_id = '006N1000006c5PpIAI';
select * from stages where crm_configuration_id = 97 and type = 'opportunity';
select * from opportunities where team_id = 120;
select * from crm_configurations crm join teams t on crm.id = t.crm_id
where 1=1
AND t.current_billing_plan IS NOT NULL
AND crm.auto_sync_activity = 0
and crm.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Exclaimer%'; # 270,205,10053,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 270
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b54df794-2a9a-4957-8d80-09a600ead5f8') = uuid; # 21637956
SELECT * FROM crm_profiles WHERE user_id = 11446;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cygnetise%'; # 372,300,12554, [EMAIL]
select * from playbooks where team_id = 372;
select * from crm_fields where crm_configuration_id = 300 and object_type = 'event'; # 141340
SELECT * FROM crm_field_values WHERE crm_field_id = 141340;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 372
and sa.provider = 'salesforce';
select * from crm_profiles where crm_configuration_id = 300;
SELECT * FROM crm_configurations WHERE team_id = 372;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Planday%'; # 291,242,11501,[EMAIL]
SELECT * FROM opportunities WHERE team_id = 291 and crm_provider_id = '006bG000005DO86QAG'; # 3207756
select * from crm_field_data where object_id = 3207756;
SELECT * FROM crm_fields WHERE id = 111834;
select f.id, f.crm_provider_id AS field_name, f.label, fd.object_id AS dealId, fd.value
FROM crm_fields f
JOIN crm_field_data fd ON f.id = fd.crm_field_id
WHERE f.crm_configuration_id = 242
AND f.object_type = 'opportunity'
AND fd.object_id IN (3207756)
ORDER BY fd.object_id, fd.updated_at;
SELECT * FROM crm_configurations WHERE auto_connect = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150,[EMAIL]
select * from group_deal_risk_types drgt join groups g on drgt.group_id = g.id
where g.team_id = 187;
select * from `groups` where team_id = 187;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 187
and sa.provider = 'salesforce';
# Destination - 98870 - Destination__c
# Stage - 79014 - StageName
# Land Arrangement - 98856 - Land_Arrangement__c
# Flight - 98848 - Flight__c
# Last activity date - 98812 - LastActivityDate
# Last modified date - 98809 - LastModifiedDate
# Last inbound mail timestamp - 99151 - Last_Inbound_Mail_Timestamp__c
# next call - 98864 - Next_Call__c
select * from crm_fields where crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
select * from opportunities where team_id = 187 and name LIKE'%Muriel Sal%';
select * from opportunities where team_id = 187 and user_id = 9951 and is_closed = 0;
select * from activities where opportunity_id = 3538248;
SELECT * FROM crm_profiles WHERE user_id = 8150;
select * from deal_risks where opportunity_id = 3538248;
select * from teams where crm_id IS NULL;
SELECT opp.id AS opportunity_id,
u.group_id AS group_id,
MAX(
CASE
WHEN a.type IN ("sms-inbound", "sms-outbound") THEN a.created_at
ELSE a.actual_end_time
END) as last_date
FROM opportunities opp
left join activities a on a.opportunity_id = opp.id
inner join users u on opp.user_id = u.id
where opp.user_id IN (9951)
AND opp.is_closed = 0
and a.status IN ('completed', 'received', 'delivered') OR a.status IS NULL
group by opp.id;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Cybsafe%'; # 343,301,12008,[EMAIL]
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_profiles WHERE crm_configuration_id = 301;
SELECT * FROM contacts WHERE id = 6612363;
SELECT * FROM accounts WHERE id = 4235676;
SELECT * FROM opportunities WHERE crm_configuration_id = 301 and crm_provider_id = 32983784868;
select * from opportunity_stages where opportunity_id = 4503759;
# SELECT * FROM opportunities WHERE id = 4569937;
select * from activities where crm_configuration_id = 301;
SELECT * FROM activities WHERE uuid_to_bin('d3b2b28b-c3d0-4c2d-8ed0-eef42855278a') = uuid; # 26330370
SELECT * FROM participants WHERE activity_id = 26330370;
SELECT * FROM teams WHERE id = 375;
select * from playbooks where team_id = 375;
select * from stages where crm_configuration_id = 301 and type = 'opportunity';
select * from teams;
select * from contact_roles;
SELECT * FROM opportunities WHERE team_id = 343 and user_id = 12871 and close_date >= '2024-11-01';
select * from users u join crm_profiles cp on cp.user_id = u.id where u.team_id = 343;
SELECT * FROM crm_field_data WHERE object_id = 3771706;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 343
and sa.provider = 'hubspot';
SELECT * FROM crm_fields WHERE crm_configuration_id = 301 and object_type = 'opportunity'
and crm_provider_id LIKE "%traffic_light%";
SELECT * FROM crm_field_values WHERE crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531);
SELECT fd.* FROM opportunities o
JOIN crm_field_data fd ON o.id = fd.object_id
WHERE o.team_id = 343
# and o.user_id IS NOT NULL
and fd.crm_field_id IN (144020,144048,144111,144113,144126,144481,144508,144531)
and fd.value != ''
order by value desc
# group by o.id
;
SELECT * FROM opportunities WHERE id = 3769843;
SELECT * FROM teams WHERE name LIKE '%Tour%'; # 187,209,8150, [EMAIL]
SELECT * FROM crm_layouts WHERE crm_configuration_id = 209;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 682;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding Circle%'; # 220,177,8603,[EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('7a40e99b-3b37-4bb1-b983-325b81801c01') = uuid; # 23139839
SELECT * FROM opportunities WHERE id = 3855992;
SELECT * FROM users WHERE name LIKE '%Angus Pollard%'; # 8988
SELECT * FROM teams WHERE name LIKE '%Story Terrace%'; # 379, 307, 12894
SELECT * FROM crm_fields WHERE crm_configuration_id = 307 and object_type != 'opportunity';
select * from contacts where team_id = 379 and name like '%bebro%'; # 5874411, crm: 77229348507
SELECT * FROM crm_field_data WHERE object_id = 5874411;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379
and sa.provider = 'hubspot';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%mentio%'; # 117, 94, 6371, [EMAIL]
SELECT * FROM activities WHERE uuid_to_bin('82939311-1af0-4506-8546-21e8d1fdf2c1') = uuid;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Tourlane%'; # 187, 209, 8150, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 187 and crm_provider_id = '006Se000008xfvNIAQ'; # 3537793
select * from generic_ai_prompts where subject_id = 3537793;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lunio%'; # 120, 97, 10984, [EMAIL]
SELECT * FROM crm_configurations WHERE id = 97;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 97;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 355;
SELECT * FROM crm_fields WHERE id = 32682;
select cfd.value, o.* from opportunities o
join crm_field_data cfd on o.id = cfd.object_id and cfd.crm_field_id = 32682
where team_id = 120
and cfd.value != ''
;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 120
and sa.provider = 'salesforce';
select * from opportunities where team_id = 120 and crm_provider_id = '006N1000007X8MAIA0';
SELECT * FROM crm_field_data WHERE object_id = 2313439;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 410;
SELECT * FROM teams WHERE name LIKE '%Local Business Oxford%';
select * from scorecards where team_id = 410;
select * from scorecard_rules;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Funding%'; # 220, 177, 8603, [EMAIL]
select * from activities a
join opportunities o on a.opportunity_id = o.id
join users u on o.user_id = u.id
where a.crm_configuration_id = 177 and a.type LIKE '%email-out%'
# and a.actual_end_time > '2024-12-16 00:00:00'
# and o.remotely_created_at > '2024-12-01 00:00:00'
# and u.group_id = 1014
and u.id = 9021
order by a.id desc;
SELECT * FROM opportunities WHERE id in (3981384,4017346);
SELECT * FROM users WHERE team_id = 220 and id IN (8775, 11435);
select * from users where id = 9021;
select * from inboxes where user_id = 9021;
select * from inbox_emails where inbox_id = 1349 and email_date > '2024-12-18 00:00:00';
select * from email_messages where team_id = 220
and orig_date > '2024-12-16 00:00:00' and orig_date < '2024-12-19 00:00:00'
and subject LIKE '%Personal%'
# and 'from' = '[EMAIL]'
;
select * from activities a
join opportunities o on a.opportunity_id = o.id
where a.user_id = 9021 and a.type LIKE '%email-out%'
and a.actual_end_time > '2024-12-18 00:00:00'
and o.user_id IS NOT NULL
and o.remotely_created_at > '2024-12-01 00:00:00'
order by a.id desc;
SELECT * FROM opportunities WHERE team_id = 220 and name LIKE '%Right Car move Limited%' and id = 3966852;
select * from activities where crm_configuration_id = 177 and type LIKE '%email%' and opportunity_id = 3966852 order by id desc;
select * from team_settings where name IN ('useCloseDate');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hurree%'; # 104, 81, 6175, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 104 and name = 'PropOp';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 104
and sa.provider = 'hubspot';
select * from crm_configurations where last_synced_at > '2025-01-19 01:00:00'
select * from teams where crm_id IS NULL;
select t.name as 'team', u.name as 'owner', u.email, u.phone
from teams t
join activity_providers ap on t.id = ap.team_id
join users u on t.owner_id = u.id
where 1=1
and t.status = 'active'
and ap.is_enabled = 1
# and u.status = 1
and ap.provider = 'ms-teams';
select * from crm_configurations where provider = 'bullhorn'; # 344
SELECT * FROM teams WHERE id = 442; # 14293
select * from users where team_id = 442;
select * from social_accounts sa where sa.sociable_id = 14293;
select * from invitations where team_id = 442;
# [PASSWORD_DOTS]
SELECT * FROM users WHERE email LIKE '%[EMAIL]%'; # 14022
SELECT * FROM teams WHERE id = 429;
select * from opportunities where team_id = 429 and crm_provider_id IN (16157415775, 22246219645);
select * from activities where opportunity_id in (4340436,4353519);
select * from transcription where activity_id IN (25630961,25381771);
select * from generic_ai_prompts where subject_id IN (4353519);
SELECT
a.id as activity_id,
a.opportunity_id,
a.type as activity_type,
a.language,
CONCAT(a.title, a.description) AS mail_content,
e.from AS mail_from,
e.to AS mail_to,
e.subject AS mail_subject,
e.body AS mail_body,
p.type as prompt_type,
p.status as prompt_status,
p.content AS prompt_content,
a.actual_start_time as created_at
FROM activities a
LEFT JOIN ai_prompts p ON a.transcription_id = p.transcription_id AND p.deleted_at IS NULL
LEFT JOIN email_messages e ON a.id = e.activity_id
WHERE a.actual_start_time > '2024-01-01 00:00:00'
AND a.opportunity_id IN (4353519)
AND a.status IN ('completed', 'received', 'delivered')
AND a.deleted_at IS NULL
AND a.type NOT IN ('sms-inbound', 'sms-outbound')
ORDER BY a.opportunity_id ASC, a.id ASC;
SELECT * FROM users WHERE name LIKE '%George Fierstone%'; # 14293
SELECT * FROM teams WHERE id = 442;
SELECT * FROM crm_configurations WHERE id = 344;
select * from team_features where team_id = 442;
select * from groups where team_id = 442;
select * from playbooks where team_id = 442;
select * from playbook_categories where playbook_id = 1729;
select * from crm_fields where crm_configuration_id = 344 and id = 172024;
SELECT * FROM crm_field_values WHERE crm_field_id = 172024;
select * from crm_layouts where crm_configuration_id = 344;
select * from playbook_layouts where playbook_id = 1729;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 221, 9444
select s.*
# , s.sent_at, u.name, a.*
from activity_summary_logs s
inner join activities a on a.id = s.activity_id
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 356
and s.sent_at > date_sub(now(), interval 60 day)
order by a.actual_end_time desc;
select * from activities a
# inner join activity_summary_logs s on s.activity_id = a.id
where a.crm_configuration_id = 356 and a.actual_end_time > date_sub(now(), interval 60 day)
# and a.crm_provider_id is not null
# and provider <> 'ringcentral'
and status = 'completed'
order by a.actual_end_time desc;
select * from teams order by id desc; # 17328, 32, 17830, [EMAIL]
SELECT * FROM users;
SELECT * FROM users where team_id = 260 and status = 1; # 201 - 150 active
SELECT * FROM teams WHERE id = 260;
select * from team_settings where team_id = 260;
select * from crm_configurations where team_id = 260;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 356;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1184;
select * from accounts where crm_configuration_id = 221 order by id desc; # 7000
select * from leads where crm_configuration_id = 221 order by id desc; # 0
select * from contacts where crm_configuration_id = 221 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 221 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 221 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 221;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 221 order by id desc;
select * from stages where crm_configuration_id = 221 order by id desc;
select * from accounts where crm_configuration_id = 356 order by id desc; # 7000
select * from leads where crm_configuration_id = 356 order by id desc; # 0
select * from contacts where crm_configuration_id = 356 order by id desc; # 200 000
select * from opportunities where crm_configuration_id = 356 order by id desc; # 0
select * from crm_profiles where crm_configuration_id = 356 order by id desc; # 23
select * from crm_fields where crm_configuration_id = 356;
select * from crm_field_values where crm_field_id = 5302 order by id desc;
select * from crm_layouts where crm_configuration_id = 356 order by id desc;
select * from stages where crm_configuration_id = 356 order by id desc;
select * from playbooks where team_id = 260 order by id desc; # 4 (2 deleted)
select * from groups where team_id = 260 order by id desc; # 27 groups, (2 deleted)
select * from playbook_layouts where playbook_id IN (1410,1409,1276,1254); # 4
select ce.* from calendars c
join users u on c.user_id = u.id
join calendar_events ce on c.id = ce.calendar_id
where u.team_id = 260
and (ce.start_time > '2025-02-21 00:00:00')
;
# calendar events 1207
#
select * from opportunities where team_id = 260;
SELECT * FROM crm_field_data WHERE object_id = 4696496;
select * from activities where crm_configuration_id = 356 and crm_provider_id IS NOT NULL;
select * from activities where crm_configuration_id IN (221) and provider NOT IN ('ms-teams', 'uploader', 'zoom-bot')
# and type = 'conference' and status = 'scheduled' and activities.is_internal = 0
and created_at > '2024-03-01 00:00:00'
order by id desc; # 880 000, ringcentral, avaya
SELECT * FROM participants WHERE activity_id = 26371744;
# all activities 942 000 +
# conference 7385 - scheduled 984 - external 343
select * from activities where id = 26321812;
select * from participants where activity_id = 26321812;
select * from participants where activity_id in (26414510,26414514,26414516,26414604,26414653,26414655);
select * from leads where id in (720428,689175,731546,645866,621037);
select * from users where id = 13841;
select * from opportunities where user_id = 9541;
select * from stages where id = 15900;
select * from accounts where
# id IN (4160055,5053725,4965303,4896434)
id in (4584518,3249934,3218025,3891133,3399450,4172999,4485161,3101785,4587203,3070816,2870343,2870341,3563940,4550846,3424464,3249963,2870342)
;
select * from activities where id = 26654935;
SELECT * FROM opportunities WHERE id = 4803458;
SELECT * FROM opportunities where team_id = 260 and user_id = 13841 AND stage_id = 15900;
SELECT id, uuid, provider, type, lead_id, account_id, contact_id, opportunity_id, stage_id, status, recording_state, title, actual_start_time, actual_end_time
FROM activities WHERE user_id = 13841 AND opportunity_id IN (4729783, 4731717, 4731726, 4732064, 4732849, 4803458, 4813213);
SELECT DISTINCT
o.id, o.stage_id, s.name, a.title,
a.*
FROM activities a
# INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
INNER JOIN groups g ON u.group_id = g.id
INNER JOIN opportunities o ON a.opportunity_id = o.id
INNER JOIN stages s ON o.stage_id = s.id
WHERE
a.crm_configuration_id = 356
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 13841
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
AND team.uuid = uuid_to_bin('a607fba7-452e-4683-b2af-00d6cb52c93c')
AND g.uuid = uuid_to_bin('b5d69e40-24a0-4c16-810b-5fa462299f94')
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-13 00:00:00' AND '2025-03-18 07:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND u.uuid = uuid_to_bin('6f40e4b8-c340-4059-b4ac-1728e87ea99e')
)
)
AND (
# s.id = 15900
s.uuid = uuid_to_bin('04ca1c26-c666-4268-a129-419c0acffd73')
OR s.uuid IS NULL -- Include records without opportunity stage
)
ORDER BY a.actual_end_time DESC;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Lead Forensics%'; # 190, 162, 8474, [EMAIL]
SELECT * FROM users WHERE team_id = 190;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 190
and sa.provider = 'hubspot';
select * from role_user where user_id = 8474;
select * from crm_configurations where provider = 'bullhorn';
SELECT * FROM opportunities WHERE uuid_to_bin('94578249-65ec-4205-90f2-7d1a7d5ab64a') = uuid;
SELECT * FROM users WHERE uuid_to_bin('26dbadeb-926f-4150-b11b-771b9d4c2f9a') = uuid;
SELECT * FROM opportunities WHERE id = 4732493;
select * from activities where opportunity_id = 4732493;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE id = 443; # 358, 14315, [EMAIL]
SELECT * FROM opportunities WHERE team_id = 443;
SELECT a.id, a.type, a.user_id, a.status, a.deleted_at, u.name, u.email, u.team_id as activity_team_id, u.status, u.deleted_at, t.name, t.status, s.team_id as stage_team_id
FROM activities AS a
JOIN stages AS s ON a.stage_id = s.id
JOIN users AS u ON u.id = a.user_id
JOIN teams AS t ON t.id = s.team_id
WHERE u.team_id <> s.team_id and t.id > 135;
SELECT
crm_configuration_id,
crm_provider_id,
COUNT(*) as duplicate_count,
GROUP_CONCAT(id) as stage_ids,
GROUP_CONCAT(name) as stage_names
FROM stages
GROUP BY crm_configuration_id, crm_provider_id
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC;
select * from stages where id IN (14898,14907);
select * from business_processes;
SELECT *
FROM crm_configurations
WHERE team_id IN (
SELECT team_id
FROM crm_configurations
GROUP BY team_id
HAVING COUNT(*) > 1
)
ORDER BY team_id;
SELECT *
FROM teams
WHERE crm_id IN (
SELECT crm_id
FROM teams
GROUP BY crm_id
HAVING COUNT(*) > 1
)
ORDER BY crm_id;
# [PASSWORD_DOTS]
select * from crm_configurations where provider = 'integration-app';
SELECT * FROM teams WHERE id = 443; # Correre Naturale 358 14315 [EMAIL]
select * from activities where crm_configuration_id = 358 order by actual_end_time desc;
select id, uuid, actual_end_time, crm_provider_id, is_internal, playbook_category_id, type, user_id, lead_id, contact_id, account_id, opportunity_id, status, title from activities where crm_configuration_id = 358 order by actual_end_time desc;
select * from team_features where team_id = 358;
select * from activity_summary_logs;
select * from teams where id = 406;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sportfive%'; # 267, 202, 14637, [EMAIL]
select * from activities where crm_configuration_id = 202 order by actual_end_time desc;
SELECT * FROM users where id = 14637;
SELECT * FROM teams where id = 267;
SELECT * FROM groups where id = 1118;
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM activities
WHERE crm_configuration_id = 202
AND status IN ('completed', 'failed')
AND recording_state != 'stopped'
AND type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
AND (is_private = 0 OR user_id = 14637)
AND (
(
actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
) OR (
actual_start_time IS NULL
AND type IN ('sms-outbound', 'sms-inbound')
AND created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND NOT EXISTS (
SELECT 1
FROM tracks
WHERE
tracks.activity_id = activities.id
AND tracks.type IN ('audio', 'video')
)
ORDER BY actual_end_time DESC;
SELECT DISTINCT
a.*
FROM activities a
INNER JOIN tracks t ON a.id = t.activity_id
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams team ON u.team_id = team.id
WHERE
a.crm_configuration_id = 202
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
# and a.user_id = 14637
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND t.type IN ('audio', 'video')
AND (
(a.actual_start_time BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59')
OR
(
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-12 12:00:00' AND '2025-03-24 11:59:59'
)
)
AND (
a.is_private = 0
OR (
a.is_private = 1
AND a.user_id = 14637
)
)
ORDER BY a.actual_end_time DESC
;
SELECT DISTINCT a.*
FROM activities a
INNER JOIN users u ON a.user_id = u.id
INNER JOIN teams t ON u.team_id = t.id
# INNER JOIN tracks tr ON a.id = tr.activity_id
# INNER JOIN groups g ON u.group_id = g.id
WHERE 1=1
AND t.id = 267
# AND t.uuid = uuid_to_bin('aed4927b-f1ea-499e-94c3-83762fd233e8')
AND a.status IN ('completed', 'failed')
AND a.recording_state != 'stopped'
AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
# AND tr.type NOT IN ('audio', 'video')
AND (
a.is_private = 0
OR a.user_id = 14637
)
AND (
(a.actual_start_time BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59')
OR (
a.actual_start_time IS NULL
AND a.type IN ('sms-outbound', 'sms-inbound')
AND a.created_at BETWEEN '2025-03-19 00:00:00' AND '2025-03-21 23:59:59'
)
)
# and NOT EXISTS (
# SELECT 1
# FROM tracks t
# WHERE t.activity_id = a.id
# AND t.type IN ('audio', 'video')
# )
ORDER BY a.actual_end_time DESC;
SELECT * FROM tracks WHERE activity_id = 26485995;
select a.is_private, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
where a.crm_configuration_id = 202
# and a.is_internal = 0
and (a.actual_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type IN ("softphone","softphone-inbound","conference","sms-inbound")
and a.status IN ('completed', 'failed')
# and a.external_id is not null
order by a.actual_end_time desc;
select * from activities a where a.crm_configuration_id = 202
and a.actual_start_time between '2025-03-20 00:00:00' and '2025-03-21 00:00:00'
# AND a.type IN ('softphone', 'softphone-inbound', 'conference', 'sms-inbound', 'sms-outbound')
select g.name, a.title, uuid_from_bin(a.uuid), a.external_id, a.status, a.recording_state, a.recording_reason_code, a.scheduled_start_time, a.scheduled_end_time, a.actual_start_time, a.actual_end_time from activities a
inner join users u on u.id = a.user_id
inner join groups g on g.id = u.group_id
where a.crm_configuration_id = 202
and a.is_internal = 0
and (a.scheduled_start_time between '2025-03-19 00:00:00' and '2025-03-21 00:00:00')
and a.type = 'conference'
and a.status != 'completed'
and a.external_id is not null
order by a.scheduled_start_time desc;
SELECT * FROM teams WHERE name LIKE '%Tourlane%';
SELECT * FROM crm_fields WHERE crm_configuration_id = 209 and object_type = 'opportunity';
SELECT * FROM crm_field_data WHERE crm_field_id = 98809;
select * from users where status = 1 AND timezone = 'MDT';
select * from opportunities where id = 3769814;
select * from deal_risks where opportunity_id = 3769814;
select cp.* from crm_profiles cp
join users u on cp.user_id = u.id
join crm_configurations crm on cp.crm_configuration_id = crm.id
where crm.provider = 'hubspot' AND u.status = 1 AND log_notes != 'none';
select * from crm_fields where id = 154575;
select * from team_features where feature = 'SUPPORTS_SYNC_MISSING_CALL_DISPOSITIONS';
SELECT * FROM teams WHERE id = 176; # crm 148
select * from activities where crm_configuration_id = 148 and provider = 'hubspot' order by id desc;
select * from activity_providers where provider = 'amazon-connect';
select * from crm_fields cf
join crm_configurations crm on crm.id = cf.crm_configuration_id
where crm.provider = 'hubspot' and cf.object_type IN ('account', 'contact');
# [PASSWORD_DOTS]
SELECT * FROM users WHERE id IN (15415, 15418);
SELECT * FROM groups WHERE id IN (1805,1806);
SELECT * FROM playbooks WHERE id = 1860;
SELECT * FROM playbook_categories WHERE id = 38634;
SELECT * FROM crm_fields WHERE id = 189962;
SELECT * FROM teams WHERE name = 'Pulsar Group'; # 472, 380, 15138 [EMAIL]
SELECT * FROM crm_profiles WHERE user_id = 15415;
SELECT * FROM social_accounts WHERE sociable_id = 15415 and provider = 'salesforce';
select * from sidekick_settings where team_id = 472;
SELECT * FROM activities WHERE uuid_to_bin('452c58c7-b87c-4fdd-953e-d7af185e9588') = uuid; # 28617536, user: 15418
SELECT * FROM activities WHERE uuid_to_bin('399114ee-d3a8-458c-bff5-5f654658db0a') = uuid; # 28344407, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('f0aa567f-0ab1-4bbb-96aa-37dcf184676b') = uuid; # 28580288, user: 15415
SELECT * FROM activities WHERE uuid_to_bin('50c086b1-2770-4bca-b5ae-6bac22ec426b') = uuid; # 28566069, user: 15415
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%TeamTailor%'; # 109, 218, 13969, [EMAIL]
select * from crm_configurations where id = 218;
SELECT * FROM activities WHERE uuid_to_bin('e39b5857-7fdb-4f5a-951a-8d3ca69bb1b0') = uuid; # 28338765
SELECT * FROM users WHERE id IN (13232, 13230);
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
0057R00000EPL5HQAX Inez Ekblad
1091cb81-5ea1-4951-a0ed-f00b568f0140 Triman Kaur
SELECT * FROM crm_profiles WHERE user_id IN (13232, 13230);
############################################################################################
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939 00UVg00000FLvnSMAT
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id IN (94491,94493,94498);
SELECT * FROM users WHERE id = 13658;
SELECT * FROM teams WHERE id = 109;
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Strengthscope%'; # 481, 390, 15420, [EMAIL]
SELECT * FROM stages WHERE crm_configuration_id = 390;
select * from business_processes where team_id = 481 and crm_configuration_id = 390;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 481
and sa.provider = 'salesforce';
SELECT * FROM users WHERE id = 15780; # team 462
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 462
and sa.provider = 'hubspot';
select * from teams where id = 495;
SELECT * FROM users WHERE id = 15794;
select * from social_accounts where sociable_id = 15794;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Flight%'; # 427, 333, 13752
SELECT * FROM accounts WHERE team_id = 427 and crm_provider_id = '668731000183444517';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Group GTI%'; # 495, 407, 15794
SELECT * FROM activities WHERE crm_configuration_id = 407
and status = 'completed' and type = 'conference'
order by id desc;
select ru.*, pr.*, p.* from users u join role_user ru on ru.user_id = u.id
join permission_role pr on pr.role_id = ru.role_id
join permissions p on p.id = pr.permission_id
where team_id = 495 and p.name IN ('dial');
select * from permission_role;
select * from activities where crm_configuration_id = 407 and status = 'completed' order by id desc;
SELECT * FROM activities WHERE id = 29512773;
SELECT * FROM activities WHERE id IN (29042721,28991325,29002874);
SELECT al.* from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 407
# and a.id IN (29042721,28991325,29002874);
SELECT * FROM users WHERE id = 15794;
SELECT * FROM users WHERE team_id = 495;
SELECT * FROM social_accounts WHERE sociable_id = 15794;
SELECT * FROM opportunities WHERE team_id = 495 and name like '%OC:%';
SELECT * FROM contacts WHERE team_id = 495;
SELECT * FROM leads WHERE team_id = 495;
SELECT * FROM accounts WHERE team_id = 495;
SELECT * FROM crm_profiles WHERE crm_configuration_id = 407;
SELECT * FROM crm_fields WHERE crm_configuration_id = 407;
SELECT * FROM crm_configurations WHERE id = 407;
SELECT * FROM opportunities WHERE team_id = 495 and close_date BETWEEN '2025-06-01' AND '2025-07-01'
and user_id IS NOT NULL and is_closed = 1 and is_won = 1;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Hamilton Court FX LLP%'; # 249, 187, 10103
SELECT * FROM activities WHERE uuid_to_bin('4659c2bb-9a49-484e-9327-a3d66f1e028c') = uuid; # 28951064
SELECT * FROM crm_fields WHERE crm_configuration_id = 187 and object_type IN ('tasks', 'event');
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Checkstep%'; # 325, 256, 11753
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 325
and sa.provider = 'hubspot';
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid; # 28611085
SELECT * FROM activities WHERE uuid_to_bin('980f0336-840b-4185-a5a9-30cf8b0749a8') = uuid; # 28719733
SELECT * FROM activity_summary_logs where activity_id = 28719733;
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Learning%'; # 260, 356, 9444
SELECT * FROM activity_summary_logs where sent_at BETWEEN '2025-06-09 11:38:00' AND '2025-06-09 11:40:00';
SELECT * FROM leads WHERE crm_configuration_id = 356 and crm_provider_id = '230045001502770504'; # 823630
select * from activities where crm_configuration_id = 356 and lead_id = 841732;
SELECT * from activity_summary_logs al join activities a on a.id = al.activity_id
where a.crm_configuration_id = 356;
select * from activities where crm_configuration_id = 356
and actual_end_time between '2025-06-09 11:00:00' and '2025-06-09 12:00:00'
order by id desc;
select * from accounts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from leads where crm_configuration_id = 356 and crm_provider_id = '230045001514275654' order by id desc;
select * from contacts where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from opportunities where crm_configuration_id = 356 and crm_provider_id = '230045001514403366' order by id desc;
select * from team_features where team_id = 260;
select * from features where id IN (1,2,4,6,18,19,20,9,10,3,23,24,25,26,27);
SELECT * FROM activities WHERE uuid_to_bin('7be372e2-1916-4d79-a2f3-ca3db1346db3') = uuid;
select * from crm_fields;
select * from crm_layout_entities;
SELECT * FROM teams WHERE name LIKE '%Optable%';
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Teamtailor%'; # 109, 218, 13969
SELECT * FROM crm_configurations WHERE id = 218;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 109
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('675eeaeb-5681-42db-90bc-54c07a604408') = uuid; # 28655939
SELECT * FROM crm_field_data WHERE activity_id = 28655939;
SELECT * FROM crm_fields WHERE id in (94491,94493,94498);
select * from teams where crm_id IS NULL;
SELECT * FROM activities WHERE uuid_to_bin('71aa8a0c-9652-4ff6-bee7-d98ae60abef6') = uuid;
# [PASSWORD_DOTS]
select * from team_domains where team_id = 399;
SELECT * FROM teams WHERE name LIKE '%Rydoo%'; # 399, 318, 13207
select * from calendar_events where id = 5163781;
SELECT * FROM activities WHERE uuid_to_bin('be2cbc52-7fda-46a0-9ae0-25d9553eafc0') = uuid; # 29443896
SELECT * FROM participants WHERE activity_id = 29443896;
select * from contacts where crm_configuration_id = 318 and email = '[EMAIL]';
select * from leads where crm_configuration_id = 318 and email = '[EMAIL]';
select * from activities where user_id = 14937 order by created_at ;
select * from users where id = 14937;
select * from contacts where crm_configuration_id = 318 and email LIKE '%@strawberry.se';
select * from opportunities where crm_configuration_id = 318 and crm_provider_id = '006Sf00000D1WOAIA3';
select * from activities a join participants p on a.id = p.activity_id
where crm_configuration_id = 318 and a.updated_at > '2025-06-23T08:18:43Z';
# [PASSWORD_DOTS]
SELECT * FROM opportunities WHERE team_id = 379 and crm_provider_id = '39334518886';
SELECT * FROM opportunities WHERE team_id = 379 order by id desc;
SELECT * FROM teams WHERE id = 379;
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 379 and sociable_id = 13852
and sa.provider = 'hubspot';
SELECT * FROM crm_configurations WHERE id = 307;
SELECT * FROM crm_layouts WHERE crm_configuration_id = 307;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 1027;
SELECT * FROM crm_fields WHERE crm_configuration_id = 307
and id IN (144750,144855,145158,155227);
SELECT * FROM activities;
select * from activities
where created_at > '2025-07-01 00:00:00'
# and created_at < '2025-08-01 00:00:00'
and type not in ('email-outbound', 'email-inbound')
and account_id is null
and contact_id is null
and lead_id is null
and opportunity_id is not null
;
SELECT * FROM activities WHERE id IN (25344155, 25344296, 25501909, 28692187);
SELECT * FROM crm_configurations WHERE id in (335,301,200);
select * from crm_fields where crm_configuration_id = 230 and crm_provider_id = 'Age2__c';
SELECT * FROM teams WHERE name LIKE '%Resights%';
select * from crm_fields where crm_configuration_id = 1 and object_type = 'opportunity';
select * from crm_configurations where provider = 'bullhorn'; # 344
select * from teams where id IN (442);
select * from activities
where crm_configuration_id = 177
and provider = 'amazon-connect'
order by id desc;
# and source <> 'gong';
select * from activity_providers where provider = 'amazon-connect';
SELECT * FROM activities WHERE uuid_to_bin('cec1993b-a7e5-4164-b74d-d680ea51d2f2') = uuid;
select * from crm_configurations where store_transcript = 1;
SELECT * FROM teams WHERE id IN (80);
# [PASSWORD_DOTS]
SELECT * FROM teams WHERE name LIKE '%Sedna%'; # 277, 213, 12594
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 277
and sa.provider = 'salesforce';
select * from activities where crm_configuration_id = 213 and account_id = 2511502;
select * from crm_configurations where id = 213;
SELECT * FROM activities WHERE uuid_to_bin('35aa790a-8569-4544-8268-66f9a4a26804') = uuid; # 33981604
SELECT * FROM participants WHERE activity_id = 33981604;
SELECT * FROM crm_fields WHERE crm_configuration_id = 337 and object_type = 'task';
select * from social_accounts sa
join users u on sa.sociable_id = u.id
where u.team_id = 431
and sa.provider = 'salesforce';
SELECT * FROM activities WHERE uuid_to_bin('b5476c7d-19a8-491b-869d-676ea1e857b6') = uuid; # 33997223
select * from activity_summary_logs where activity_id = 33997223;
select * from activity_notes where activity_id =...
|
NULL
|
|
11026
|
218
|
1
|
2026-04-14T09:09:17.295322+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157757295_m1.jpg...
|
PhpStorm
|
faVsco.js – ActivitySearch.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests passed: 1
text/html
text/html
text/html
Proj Tests passed: 1
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests passed: 1","depth":3,"value":"Tests passed: 1","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1117453743363212307
|
-7190066597166601333
|
click
|
hybrid
|
NULL
|
Tests passed: 1
text/html
text/html
text/html
Proj Tests passed: 1
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
+SlackEDHomeDMSActivityFilesLater..•More+FileEditViewGoHistoryWindowHelpJiminny ...# Starredplatform-inner-teamChannels# ai-chapter# alerts# backend# confusion-clinic# curiosity_lab# engineering# frontend# general# infra-changes# jiminny-bg# platform-tickets# product launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi…..Direct messagesAneliya Angelova, ...Steliyan GeorgievAdelina Petrova, Ili.... Adelina PetrovaO. Calea Dimitravo→Search Jiminny IncAneliya Angelova, Nikolay Yankov, Steliyan GeorgievMessagesAdd canvas+Nikolay Yankov 10:45 AMпиши кат оя рьннешLukas Kovalik 10:52 AMзабавих се че ми се разбазикаха settings за средипуснах и мина и fail-наима result но e failedзначиREASON_NOT_ENOUGH_ACTIVITIESвиж дали има нещо в OD със този филтьрNikolay Yankov 11:01 AMДобреNikolay Yankov 11:39 AMя рьнни пак LukasLukas Kovalik 11:43 AMготовосьщотоCompetitive pitches беше втория нали такаNikolay Yankov 12:04 PMДа, там има 14 активитита, защо не сработи този пьт?Lukas Kovalik 12:05 PMпак изглежда sequenceгледам гоMessage Aneliya Angelova, Nikolay Yankov, Steliyan Georgiev+Aa(ahlSupport Daily - in 2h 51 m100% C4Tue 14 Apr 12:09:1686 0Today ~...
|
11023
|
|
11027
|
219
|
3
|
2026-04-14T09:09:17.318819+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776157757318_m2.jpg...
|
PhpStorm
|
faVsco.js – ActivitySearch.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests passed: 1
text/html
text/html
text/html
Proj Tests passed: 1
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests passed: 1","depth":3,"bounds":{"left":0.94257814,"top":0.11180556,"width":0.0375,"height":0.011805556},"value":"Tests passed: 1","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.94257814,"top":0.11180556,"width":0.0375,"height":0.011805556},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14960937,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny, but local branch is out of sync with remote","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.7589844,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceT…Defaults","depth":6,"bounds":{"left":0.7769531,"top":0.017361112,"width":0.12382813,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.940625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1117453743363212307
|
-7190066597166601333
|
click
|
hybrid
|
NULL
|
Tests passed: 1
text/html
text/html
text/html
Proj Tests passed: 1
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceT…Defaults
Rerun 'PHPUnit: AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
Debug 'AskJiminnyReportActivityServiceTest.tes…uenceNumberToDisableFirstRequestDefaults'
Stop 'AskJiminnyReportActivityServiceTest.testGetActivityIdsPassesNonZeroSequenceNumberToDisableFirstRequestDefaults'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
PhpStormFileEditViewNavigateCodeLaravelRefactorFV faVsco.s v#11894 on JY-18909-automated-reports-ask-iminny K vProject v© ReportController.phpToolsWindowHelpJiminnybeouecommana.ong= custom.log= laravel.logA SF [jiminny@localhost]A HS_local [jiminny@localhost]A console [PROD]v _ ServiceAulomaleakeporscommand.ono< console LUlconsole SlAGiNG© AskJiminnyReportActivityService.php© ActivitySearch.php x© ActivityApiSearch.ph© ActivitySearch.php© AutomatedReportsSendCommand.php© AddLayoutEntities.php© AskJiminnyReportActivityServiceTest.phpRequestGenerateAskJiminnyReportJobTest.php© UserOptionsByGrour© Team.php© AutomatedReportsRepository.php X<?php© AbstractStageFilterDefil© AutomatedReportsService.phpC CreateHeldActivityEvent.phpActivitySearchServicePrdeclare(strict_types=1);C DeallnsightsPeriodFilter(e) TrackProvidernstallled-vent.ono0 DealinsidnisPeriodriltennamespace Jiminny\Component\ActivitySearch\Service;g FilterDetinition.php© CreateActivityLoggedEvent.php(©) ActivityLogged.php© UserPilotActivityListener.php(C) AutomatedReportsCallbackService.phpc rterverntoncolectousec rterverntoncuev.on© FilterDefinitionQueryColclass ActivitySearch© AutomatedReportResult.php• FilteredValueContainerli© IntMinMaxRange.php© RequestGenerateAskJiminnyReportJob.phpRequestGenerateReportJob.php(C) AutomatedReport.php[ AiActivityTypeclass Automacedкeрortskepo o:B15 X4 ^7 usagesprivate Container $container;[ AiAutomationD AiCallScoringpublic function __construct(Container $container){.}* Oreturn AutomatedReportD AskAnythingD AskJiminnyAipublic function getOnDemandPageFilters(): FilterDefinitionCollectionf...}DAWS• BillingManagementCache• CoachingFeedback> D CountryD CustomerApi35373839• DatabaseC DatadogC DateTimeDeallnsights• Dealkisks> IEasuicsearchpublic function create(array $data): Automate106107/**-108* Find an automated report by UUID109110* @param string $uvid111112* Oreturn AutomatedReport|null113114public function findByUuid(string $uvid): ?Al]115return AutomatedReport: :where('uvid', Aut118public function get0nDemandPageFilterSet(Criteria $criteria, User $consumer): FilterDefinitreturn $this->getOnDemandPageFilters()-›withCriteria($criteria)-›withConsumer ($consumer)->withRestrictions($consumer->getTeam()):› D Eloquent> DEncoding9 usages1 usage> M Encryption117public function getArrayFilterKeys(User $consumer): arrayf...}DESpubLie tunction tinabytaurousdlstring eiaurul 136> D Faker1 usage• FeatureFlagsif (is_numeric($id0rUuid)) {return AutomatedReport:: find((int) $1]private function getTeamInsightsPageFilters(bool $isExport = false): FilterDefinitionCollecO FFMpeg• FileSystemD GeckoD GongGuzzleHttp198return AutomatedReport: :where('uvid', Aut199200KeyPoints• Kiosk1 usageprivate function getDealInsightsPageFilters(bool $useCreatedDate = false,booL pincLudebeallype = ralse.oool mneuudertoeune = tause): FilterDefinitionCollection {..}M LanquadeDetectionLiveFeedD Locks• Math• MediaPipeline• MeetingBot_ MobileSettings| ModelNotiticationD Nudge> [ ParagraphBreakerM ParticipantSpeechPartitionedCookie12402* Retrieve all standard (non-ASk Liminay) al 232* oodral sarno osorccocunn233The column tl 234*dearal string ssortosrection the sort dam 235236* Oreturn Collection<AutomatedReport>237— 24412 usagespublic function getAllStandardReports(string $sortColumn = 'created_at',string $sortDirection = 'desc'): Collection f...}/**245— 2462471248public function getTeamInsightsPageFilterSet(Criteria $criteria,User $consumer,bool $isExport = false): FilterDefinitionCollection i...rpublic function getDealInsightsPageFilterSet(Criteria $criteria,user consunen): FilterDefinitionCollectionf...}D PlaybackPageD PlaylistL07* Retrieve all ASK Lininny reports created k2601 usagepublic function getTeamAiAutomationFilterSet(Criteria $criteria,User $consumerWindsurf changelog 2.12.21: A new version is available. // View Changelog (today 10:33)40j Support Daily • in 2h 51 mAL AskJiminnyReportActivityServiceT….Defaults100% |45]Tue 14 Apr 12:09:16CascadeFixing ReportControllerAutomated Report MarFixing Ask Jiminny Rep+D ...Now let's run the test to verifvtests/Unxt/Servsces/Kiosk/Automate/meports/AskJ1minnyreportActivätyserviceTest-php --no-coverageloul nor oen Indlr tler var w nunwarusanCommand dockerdoster nxe ceockes /kJosk/AuP /ape/a/poras/A5kJ1minyReportActivityServiceTest. php --no-coverageCould not open input file: /app/artisanCommand dockeroocker exec oocker vand ioash -d"cd /var/www&& php artisan testLests unlu services Klosk Aucomaceakeporcs AskJiminnykeportAccivityservicelest.ono =-no-coverade D&l'Could not open input file: artisanCommand docken• docker exec docker_lamp_1 bash -c "find / -name artisan -maxdepth 6 2>/dev/null | head -5"/home/jiminny/artisan"cd /homee/jiminny && php artisan testtests/nxe erckee/Kio5/AutonateReports/5KJimamyReportActivaty ServiceTest, php --no-coverage 2561"PASSgetTests\Unit\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityServiceTestemoty when no resultsE tatt tnt hperiod date filtersids bassess snazer fsequence number to disable first _ 0.025All 8 tests pass. Here's a summary:Root Cause & FixRoot cause: buildReguestParamsFromSearch never set sequence number in the reguest params, so Criteria: : createFromkequest defaulted it to o .Since isFirstRequest() returns true when sequenceNumber === 0, several implicit defaults were silently applied:Activitvourat ionRande → min duration = 1 Tilters out o-second activitiesi• ActivityRecorded → only_recorded = 1 (restricts to recorded activities only)OrganiserGroupin → apolies user's derault croup as a tiltelThe frontend URL you noticed uses sequence_number=4, which disables these defaults - explaining why the frontend returned activities but the servicereturee o.Fix: AskJiminnyReportActivityService.php:51—added 'sequence_number' » 1 to the Criteria::createFromRequest merge, ensuring isFirstRequest() returns false and no implicit defaults are injected.lest added: testbetacc1cylasPassesNonzerosequencenumberloDisablerarsckequestberaulus assers that the captured uriteria ob ect nas 1srirstRequest () === false.Öul ..Vew allliItlle win changesvapp/Services/Kiosk/AutomatedReports/D AskJiminnyReportActivityService.php +1 -1Reject allAccept allAsk anything (2*L)+ <> Code Claude Sonnet 4.6Winasun leams11O:24uir-o( 4 spaces...
|
NULL
|
|
65956
|
NULL
|
0
|
2026-04-21T13:19:20.282909+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776777560282_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepositoryTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 2, passed: 15, ignored: 4","depth":3,"value":"Tests failed: 2, passed: 15, ignored: 4","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsRepositoryTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsRepositoryTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsRepositoryTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"5","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","depth":4,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
144942226675562142
|
-6932414698814794163
|
idle
|
accessibility
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65951
|
|
65958
|
1475
|
0
|
2026-04-21T13:19:50.780393+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776777590780_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepositoryTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 2, passed: 15, ignored: 4","depth":3,"value":"Tests failed: 2, passed: 15, ignored: 4","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsRepositoryTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsRepositoryTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsRepositoryTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"5","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","depth":4,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
144942226675562142
|
-6932414698814794163
|
idle
|
accessibility
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
65966
|
1475
|
4
|
2026-04-21T13:20:31.563290+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776777631563_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepositoryTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 2, passed: 15, ignored: 4","depth":3,"value":"Tests failed: 2, passed: 15, ignored: 4","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsRepositoryTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsRepositoryTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsRepositoryTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"5","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","depth":4,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
144942226675562142
|
-6932414698814794163
|
click
|
accessibility
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
65957
|
NULL
|
0
|
2026-04-21T13:19:21.553709+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776777561553_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepositoryTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 2, passed: 15, ignored: 4","depth":3,"bounds":{"left":0.9065825,"top":0.12849163,"width":0.07646277,"height":0.013567438},"value":"Tests failed: 2, passed: 15, ignored: 4","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.9065825,"top":0.12849163,"width":0.07646277,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8161569,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsRepositoryTest","depth":6,"bounds":{"left":0.83144945,"top":0.019952115,"width":0.084109046,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsRepositoryTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsRepositoryTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.44082448,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"5","depth":4,"bounds":{"left":0.4507979,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.46077126,"top":0.17478053,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.47007978,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.47972074,"top":0.17318435,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4870346,"top":0.17318435,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.4956782,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.5043218,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.5152925,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.52393615,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.5325798,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.54355055,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.55452126,"top":0.14844373,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.58111703,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.59208775,"top":0.14844373,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.6599069,"top":0.14844373,"width":0.02825798,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.63231385,"top":0.17318435,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.64394945,"top":0.17318435,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.6555851,"top":0.17318435,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.6655585,"top":0.17318435,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.67519945,"top":0.17158818,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6825133,"top":0.17158818,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","depth":4,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
144942226675562142
|
-6932414698814794163
|
idle
|
accessibility
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65955
|
|
65959
|
1476
|
0
|
2026-04-21T13:19:52.163831+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776777592163_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepositoryTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 2, passed: 15, ignored: 4","depth":3,"bounds":{"left":0.9065825,"top":0.12849163,"width":0.07646277,"height":0.013567438},"value":"Tests failed: 2, passed: 15, ignored: 4","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.9065825,"top":0.12849163,"width":0.07646277,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8161569,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsRepositoryTest","depth":6,"bounds":{"left":0.83144945,"top":0.019952115,"width":0.084109046,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsRepositoryTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsRepositoryTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.44082448,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"5","depth":4,"bounds":{"left":0.4507979,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.46077126,"top":0.17478053,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.47007978,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.47972074,"top":0.17318435,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4870346,"top":0.17318435,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.4956782,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.5043218,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.5152925,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.52393615,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Open Query Execution Settings…","depth":4,"bounds":{"left":0.5325798,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"In-Editor Results","depth":4,"bounds":{"left":0.54355055,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Tx: Auto","depth":4,"bounds":{"left":0.55452126,"top":0.14844373,"width":0.024268618,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel Running Statements","depth":4,"bounds":{"left":0.58111703,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Playground","depth":4,"bounds":{"left":0.59208775,"top":0.14844373,"width":0.029587766,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"jiminny","depth":4,"bounds":{"left":0.6599069,"top":0.14844373,"width":0.02825798,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"18","depth":4,"bounds":{"left":0.63231385,"top":0.17318435,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.64394945,"top":0.17318435,"width":0.009640957,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.6555851,"top":0.17318435,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"4","depth":4,"bounds":{"left":0.6655585,"top":0.17318435,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.67519945,"top":0.17158818,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.6825133,"top":0.17158818,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","depth":4,"value":"SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o\nJOIN activities a ON o.id = a.opportunity_id\nWHERE a.crm_configuration_id = 39\nAND a.actual_start_time > '2025-10-13'\nAND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM activities\nWHERE crm_configuration_id = 39 and user_id = 143\nand actual_start_time >= '2025-10-13'\nAND type IN ('conference', 'softphone-inbound', 'softphone-outbound')\n;\n\nSELECT * FROM opportunities WHERE account_id IN (178);\nselect * from activities where id IN (620137, 620187, 620188, 620189, 620230);\n\n# HS\nSELECT * FROM opportunities WHERE id IN (238);\nselect * from activities where id IN (477,2076);\n\nselect * from users;\n\nSELECT COUNT(*) FROM users;\nSELECT COUNT(*) FROM activities;\nSELECT COUNT(*) FROM opportunities;\n\nUPDATE activities\nSET\n actual_start_time = '2025-12-19 09:00:00',\n actual_end_time = '2025-12-19 10:30:00',\n scheduled_start_time = '2025-12-19 09:00:00',\n scheduled_end_time = '2025-12-19 10:30:00'\nWHERE id IN (407509,407375);\n\nselect * from partners;\n\nSELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id\nFROM activities\nWHERE user_id = 143\nAND actual_start_time >= '2025-10-13 00:00:00'\nAND actual_start_time <= '2026-01-13 23:59:59'\nORDER BY actual_start_time DESC;\n\nSELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;\nSELECT * FROM crm_layouts where crm_configuration_id = 39;\nSELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;\n# lead_id\n# account_id 177\n# contact_id 3969\n# opportunity_id\n# stage_id 203\n\nSELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;\n\nSELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'\nAND user_id = 143 and actual_start_time >= '2025-10-13';\n\nSELECT * FROM activities a\n# JOIN opportunities o ON a.opportunity_id = o.id\nWHERE a.crm_configuration_id = 39 AND a.type = 'conference'\nand status = 'completed' and recording_state = 'recorded'\nand a.actual_start_time >= '2025-10-13'\nAND a.user_id = 143\n;\n\nselect * from leads\nwhere crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707\n\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);\nSELECT * FROM activities WHERE id IN (356013,616188,616202,616310);\nSELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198\nSELECT * FROM activities WHERE id IN (356001, 356008); # contacts:\n\nSELECT * FROM opportunities WHERE id IN (1707);\nSELECT * FROM stages where id IN (204, 198);\nSELECT * FROM opportunities WHERE account_id IN (178);\nSELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';\nSELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal\n\nSELECT * FROM activities where crm_configuration_id = 39\nAND opportunity_id IS NULL\nAND is_internal = false\nand status = 'completed' and recording_state = 'recorded'\nAND actual_start_time >= '2025-10-13'\nAND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)\n# AND lead_id IN (112, 109)\n;\n\nSELECT * FROM crm_profiles WHERE user_id = 143;\n\nselect * from inboxes; # 212\nselect * from users where id = 143; # 143\nselect * from inbox_email_batches where inbox_id = 212\nand updated_at >= '2026-01-28 00:00:00' order by id desc;\nselect * from inbox_emails where inbox_id = 212\nand batch_id = 95885 order by id desc;\nselect * from email_messages where origin_user_id = 143;\nselect * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';\nselect * from participants where activity_id = 620247;\n\nselect * from crm_profiles where user_id = 143;\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001\nselect * from transcription where activity_id = 356001; # 6943\nselect * from ai_prompts where transcription_id = 6943;\nSELECT * FROM activity_summary_logs where activity_id = 356001;\n\nSELECT * FROM social_accounts WHERE sociable_id = 143;\n\n# ************************************************************************************\nSELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;\n# 422515 softphone tr. 8100\n\nSELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;\n# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS\n\nselect * from ai_prompts where transcription_id IN (8100, 7670);\nselect * from activity_summary_logs where activity_id = 407509;\n\nselect * from sidekick_settings;\nselect * from default_activity_types;\n\nSELECT * FROM contacts WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\nSELECT * FROM leads WHERE crm_configuration_id = 39 and email = 'm.kogoj@gmx.at';\n\nSELECT * FROM activity_searches where user_id = 143;\nSELECT * FROM groups where team_id = 1;\n\nselect * from teams where id = 1;\nselect * from groups where team_id = 1; # 1150 - 7e75f8025c22\nselect id, name, group_id, status, deleted_at, email\nfrom users where team_id = 1 order by group_id desc ;\n\nselect * from activity_searches where id in (1977, 1978, 1979);\nselect * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);\nselect * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277\nselect * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879\n\nINSERT INTO `activity_search_filters`\n(`activity_search_id`, `filter`, `value`) VALUES\n(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),\n(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')\n;\n\nselect * from crm_configurations where id = 39;\n\n\nselect sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id\nwhere u.team_id = 1;\nSELECT * FROM social_accounts WHERE sociable_id = 1635;\nSELECT * FROM users WHERE id = 1635;\n\nselect * from teams where id = 1;\nselect * from users where team_id = 1;\nselect * from team_features where team_id = 1;\nselect * from features;\n\nSELECT * FROM activity_searches where id = 1982; # 1981\nSELECT * FROM activity_search_filters WHERE activity_search_id = 1982;\n\nSELECT * FROM automated_reports where id = 68;\nUPDATE automated_reports set playbook_categories = NULL where id = 68;\nSELECT * FROM automated_report_results where id = 275;\n\nSELECT * FROM automated_reports order by id desc;\nSELECT * FROM automated_report_results order by id desc;\nselect * from activity_searches where user_id = 143;\nselect * from ask_anything_prompts;\n\nSELECT * FROM groups WHERE id = 1439;\nSELECT * FROM users WHERE group_id = 1439;\n\nselect * from permissions; # 158\nselect * from roles;\nselect * from permission_role\n\nselect * from teams where id = 1;\nselect * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;\nselect * from groups where id = 28;\nselect * from playbooks where team_id = 1;\nselect * from playbooks where id = 179;\nselect * from playbook_categories where id = 1391;\nselect * from users where id = 143;\nselect * from crm_profiles where user_id = 143;\nselect * from activities where crm_configuration_id = 39 and type = 'conference'\nand crm_provider_id IS NOT NULL ORDER by id desc;\nselect * from activities where id = 422003; # 00UO400000pB6fpMAC\n\nSELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type\nFROM automated_report_results ar\nJOIN automated_reports a ON a.id = ar.report_id\nWHERE a.type = 'ask_jiminny'\nLIMIT 10;\n\nSELECT `automated_report_results`.* FROM `automated_report_results`\nINNER JOIN `automated_reports`\n ON `automated_report_results`.`report_id` = `automated_reports`.`id`\nWHERE `automated_report_results`.`generated_at` IS NOT NULL\n AND `automated_reports`.`team_id` = 1\n AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$.\"users\"')\n;\n\n\nselect * from teams where id = 3143;\nselect * from crm_configurations where id = 500;\nselect * from users where name = 'Integration Account'; # 1695\nSELECT * FROM social_accounts WHERE sociable_id = 1695;\n\nselect * from activities where crm_configuration_id = 39\nand recording_state = 'recorded' and duration > 60\nand status = 'completed' and actual_start_time >= '2025-12-01';\n\nSELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;\n\nselect * from leads;","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
144942226675562142
|
-6932414698814794163
|
idle
|
accessibility
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters
Open Query Execution Settings…
In-Editor Results
Tx: Auto
Cancel Running Statements
Playground
jiminny
Sync Changes
Hide This Notification
Code changed:
Hide
18
14
2
4
Previous Highlighted Error
Next Highlighted Error
SELECT a.id, a.uuid, a.actual_start_time, o.id, o.uuid FROM opportunities o
JOIN activities a ON o.id = a.opportunity_id
WHERE a.crm_configuration_id = 39
AND a.actual_start_time > '2025-10-13'
AND a.type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM activities
WHERE crm_configuration_id = 39 and user_id = 143
and actual_start_time >= '2025-10-13'
AND type IN ('conference', 'softphone-inbound', 'softphone-outbound')
;
SELECT * FROM opportunities WHERE account_id IN (178);
select * from activities where id IN (620137, 620187, 620188, 620189, 620230);
# HS
SELECT * FROM opportunities WHERE id IN (238);
select * from activities where id IN (477,2076);
select * from users;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM activities;
SELECT COUNT(*) FROM opportunities;
UPDATE activities
SET
actual_start_time = '2025-12-19 09:00:00',
actual_end_time = '2025-12-19 10:30:00',
scheduled_start_time = '2025-12-19 09:00:00',
scheduled_end_time = '2025-12-19 10:30:00'
WHERE id IN (407509,407375);
select * from partners;
SELECT id, uuid, type, actual_start_time, user_id, crm_configuration_id
FROM activities
WHERE user_id = 143
AND actual_start_time >= '2025-10-13 00:00:00'
AND actual_start_time <= '2026-01-13 23:59:59'
ORDER BY actual_start_time DESC;
SELECT * FROM activities WHERE uuid_to_bin('78eda160-3086-435f-88a5-bb0c71b6008d') = uuid;
SELECT * FROM crm_layouts where crm_configuration_id = 39;
SELECT * FROM crm_layout_entities WHERE crm_layout_id = 282;
# lead_id
# account_id 177
# contact_id 3969
# opportunity_id
# stage_id 203
SELECT * FROM opportunities WHERE opportunities.crm_configuration_id = id = 282;
SELECT * FROM activities where crm_configuration_id = 39 AND type = 'conference'
AND user_id = 143 and actual_start_time >= '2025-10-13';
SELECT * FROM activities a
# JOIN opportunities o ON a.opportunity_id = o.id
WHERE a.crm_configuration_id = 39 AND a.type = 'conference'
and status = 'completed' and recording_state = 'recorded'
and a.actual_start_time >= '2025-10-13'
AND a.user_id = 143
;
select * from leads
where crm_configuration_id = 39; # 112 -> ac. 178, 109 => op. 1707
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310,407509,407375,356001,356008);
SELECT * FROM activities WHERE id IN (356013,616188,616202,616310);
SELECT * FROM activities WHERE id IN (407509,407375); # leads: 112, 109 | status - 198
SELECT * FROM activities WHERE id IN (356001, 356008); # contacts:
SELECT * FROM opportunities WHERE id IN (1707);
SELECT * FROM stages where id IN (204, 198);
SELECT * FROM opportunities WHERE account_id IN (178);
SELECT * FROM opportunities WHERE crm_configuration_id = 39 AND created_at > '2025-01-01';
SELECT * FROM contacts WHERE account_id IN (178); # 4118 Musaibe, 4448 Ceco Personal
SELECT * FROM activities where crm_configuration_id = 39
AND opportunity_id IS NULL
AND is_internal = false
and status = 'completed' and recording_state = 'recorded'
AND actual_start_time >= '2025-10-13'
AND (lead_id IS NOT NULL OR contact_id IS NOT NULL OR account_id IS NOT NULL)
# AND lead_id IN (112, 109)
;
SELECT * FROM crm_profiles WHERE user_id = 143;
select * from inboxes; # 212
select * from users where id = 143; # 143
select * from inbox_email_batches where inbox_id = 212
and updated_at >= '2026-01-28 00:00:00' order by id desc;
select * from inbox_emails where inbox_id = 212
and batch_id = 95885 order by id desc;
select * from email_messages where origin_user_id = 143;
select * from activities where user_id = 143 and updated_at >= '2026-01-28 00:00:00';
select * from participants where activity_id = 620247;
select * from crm_profiles where user_id = 143;
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid; # 356001
select * from transcription where activity_id = 356001; # 6943
select * from ai_prompts where transcription_id = 6943;
SELECT * FROM activity_summary_logs where activity_id = 356001;
SELECT * FROM social_accounts WHERE sociable_id = 143;
# [PASSWORD_DOTS]
SELECT * FROM activities WHERE uuid_to_bin('0164a4fb-cb95-454e-9edd-4d804e4999bd') = uuid;
# 422515 softphone tr. 8100
SELECT * FROM activities WHERE uuid_to_bin('7520add8-8d87-41a5-98e5-fc4edf96f21e') = uuid;
# 407509 conference tr. 7670 crmId: 00UD1000002J9aTMAS
select * from ai_prompts where transcription_id IN (8100, 7670);
select * from activity_summary_logs where activity_id = 407509;
select * from sidekick_settings;
select * from default_activity_types;
SELECT * FROM contacts WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM leads WHERE crm_configuration_id = 39 and email = '[EMAIL]';
SELECT * FROM activity_searches where user_id = 143;
SELECT * FROM groups where team_id = 1;
select * from teams where id = 1;
select * from groups where team_id = 1; # 1150 - 7e75f8025c22
select id, name, group_id, status, deleted_at, email
from users where team_id = 1 order by group_id desc ;
select * from activity_searches where id in (1977, 1978, 1979);
select * from activity_search_filters where activity_search_id IN (1977, 1978, 1979);
select * from activity_search_filters where filter = 'group_id' and value = '443f26b8-8512-437e-a9f9-7e75f8025c22'; # 10268, 10272, 10277
select * from nudges where activity_search_id IN (1977, 1978, 1979); # 877, 878, 879
INSERT INTO `activity_search_filters`
(`activity_search_id`, `filter`, `value`) VALUES
(1977, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1978, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22'),
(1979, 'group_id', '443f26b8-8512-437e-a9f9-7e75f8025c22')
;
select * from crm_configurations where id = 39;
select sa.* from users u JOIN social_accounts sa on u.id = sa.sociable_id
where u.team_id = 1;
SELECT * FROM social_accounts WHERE sociable_id = 1635;
SELECT * FROM users WHERE id = 1635;
select * from teams where id = 1;
select * from users where team_id = 1;
select * from team_features where team_id = 1;
select * from features;
SELECT * FROM activity_searches where id = 1982; # 1981
SELECT * FROM activity_search_filters WHERE activity_search_id = 1982;
SELECT * FROM automated_reports where id = 68;
UPDATE automated_reports set playbook_categories = NULL where id = 68;
SELECT * FROM automated_report_results where id = 275;
SELECT * FROM automated_reports order by id desc;
SELECT * FROM automated_report_results order by id desc;
select * from activity_searches where user_id = 143;
select * from ask_anything_prompts;
SELECT * FROM groups WHERE id = 1439;
SELECT * FROM users WHERE group_id = 1439;
select * from permissions; # 158
select * from roles;
select * from permission_role
select * from teams where id = 1;
select * from groups g JOIN playbooks p on g.playbook_id = p.id where g.team_id = 1;
select * from groups where id = 28;
select * from playbooks where team_id = 1;
select * from playbooks where id = 179;
select * from playbook_categories where id = 1391;
select * from users where id = 143;
select * from crm_profiles where user_id = 143;
select * from activities where crm_configuration_id = 39 and type = 'conference'
and crm_provider_id IS NOT NULL ORDER by id desc;
select * from activities where id = 422003; # 00UO400000pB6fpMAC
SELECT ar.id, ar.uuid, ar.media_type, ar.status, a.type
FROM automated_report_results ar
JOIN automated_reports a ON a.id = ar.report_id
WHERE a.type = 'ask_jiminny'
LIMIT 10;
SELECT `automated_report_results`.* FROM `automated_report_results`
INNER JOIN `automated_reports`
ON `automated_report_results`.`report_id` = `automated_reports`.`id`
WHERE `automated_report_results`.`generated_at` IS NOT NULL
AND `automated_reports`.`team_id` = 1
AND JSON_CONTAINS(`automated_reports`.`recipients`, 1635, '$."users"')
;
select * from teams where id = 3143;
select * from crm_configurations where id = 500;
select * from users where name = 'Integration Account'; # 1695
SELECT * FROM social_accounts WHERE sociable_id = 1695;
select * from activities where crm_configuration_id = 39
and recording_state = 'recorded' and duration > 60
and status = 'completed' and actual_start_time >= '2025-12-01';
SELECT * FROM activities WHERE uuid_to_bin('458cf915-b914-4000-b083-5687b32b2956') = uuid;
select * from leads;
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
|
65965
|
1476
|
3
|
2026-04-21T13:20:31.297987+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-21/1776 /Users/lukas/.screenpipe/data/data/2026-04-21/1776777631297_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsRepositoryTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 2, passed: 15, ignored: 4","depth":3,"bounds":{"left":0.9065825,"top":0.12849163,"width":0.07646277,"height":0.013567438},"value":"Tests failed: 2, passed: 15, ignored: 4","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.9065825,"top":0.12849163,"width":0.07646277,"height":0.013567438},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.12134308,"height":0.025538707},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8161569,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsRepositoryTest","depth":6,"bounds":{"left":0.83144945,"top":0.019952115,"width":0.084109046,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsRepositoryTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsRepositoryTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"3","depth":4,"bounds":{"left":0.44082448,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"5","depth":4,"bounds":{"left":0.4507979,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.46077126,"top":0.17478053,"width":0.00731383,"height":0.015163607},"role_description":"text"},{"role":"AXStaticText","text":"9","depth":4,"bounds":{"left":0.47007978,"top":0.17478053,"width":0.007978723,"height":0.015163607},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.47972074,"top":0.17318435,"width":0.00731383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.4870346,"top":0.17318435,"width":0.006981383,"height":0.018355945},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","depth":4,"bounds":{"left":0.13863032,"top":0.0,"width":0.35538563,"height":1.0},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Repositories;\n\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Illuminate\\Support\\Collection as SupportCollection;\nuse Illuminate\\Support\\Facades\\DB;\nuse Jiminny\\Models\\AutomatedReport;\nuse Jiminny\\Models\\AutomatedReportResult;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse ReflectionMethod;\nuse Tests\\TestCase;\n\nclass AutomatedReportsRepositoryTest extends TestCase\n{\n protected function setUp(): void\n {\n parent::setUp();\n $this->withoutMockingConsoleOutput();\n }\n\n /**\n * Test the update method.\n */\n public function testUpdate(): void\n {\n // Create a mock of AutomatedReport\n $reportMock = $this->createMock(AutomatedReport::class);\n\n // Set up the update method to return true\n $reportMock->expects($this->once())\n ->method('update')\n ->with(['type' => 'updated_type'])\n ->willReturn(true);\n\n // Create the repository and call the update method\n $repository = new AutomatedReportsRepository();\n $result = $repository->update($reportMock, ['type' => 'updated_type']);\n\n // Assert that the result is the report mock\n $this->assertSame($reportMock, $result);\n }\n\n /**\n * Test the create method by mocking the static create method.\n */\n public function testCreate(): void\n {\n $data = ['team_id' => 1, 'type' => 'test_type'];\n $report = $this->createMock(AutomatedReport::class);\n\n // Use reflection to replace the create method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['create'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('create')\n ->with($data)\n ->willReturn($report);\n\n $result = $repository->create($data);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when a report is found.\n */\n public function testFindByUuidWithExistingReport(): void\n {\n $uuid = 'test-uuid';\n $report = $this->createMock(AutomatedReport::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn($report);\n\n $result = $repository->findByUuid($uuid);\n $this->assertSame($report, $result);\n }\n\n /**\n * Test the findByUuid method when no report is found.\n */\n public function testFindByUuidWithNonExistingReport(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findByUuid($uuid);\n $this->assertNull($result);\n }\n\n public function testGetAllStandardReports(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n $this->assertSame($collection, $result);\n }\n\n /**\n * Test the createResult method.\n */\n public function testCreateResult(): void\n {\n $data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['createResult'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('createResult')\n ->with($data)\n ->willReturn($reportResult);\n\n $result = $repository->createResult($data);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when a result is found.\n */\n public function testFindResultByUuidWithExistingResult(): void\n {\n $uuid = 'test-uuid';\n $reportResult = $this->createMock(AutomatedReportResult::class);\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn($reportResult);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertSame($reportResult, $result);\n }\n\n /**\n * Test the findResultByUuid method when no result is found.\n */\n public function testFindResultByUuidWithNonExistingResult(): void\n {\n $uuid = 'non-existing-uuid';\n\n // Use a partial mock of the repository to test the method\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['findResultByUuid'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('findResultByUuid')\n ->with($uuid)\n ->willReturn(null);\n\n $result = $repository->findResultByUuid($uuid);\n $this->assertNull($result);\n }\n\n /**\n * Test the getReportIdsByTeam method.\n */\n public function testGetReportIdsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportsByTeam method.\n */\n public function testGetReportsByTeam(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getResultsByReport method.\n */\n public function testGetResultsByReport(): void\n {\n // Skip this test since it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');\n }\n\n /**\n * Test the getReportResultsQueryForRetention method.\n */\n public function testGetReportResultsQueryForRetention(): void\n {\n // Skip this test for now - it requires more complex mocking\n $this->markTestSkipped('This test requires more complex mocking of query builder');\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method without team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithoutFilter(): void\n {\n // Setup\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // No 'where' call expected\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults();\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n /**\n * Test the getTeamIdsWithReportsResults method with team ID filter.\n */\n public function testGetTeamIdsWithReportsResultsWithFilter(): void\n {\n // Setup\n $teamId = 123;\n $expectedCollection = Mockery::mock(SupportCollection::class);\n\n // Mock DB facade\n $queryBuilder = Mockery::mock('Illuminate\\Database\\Query\\Builder');\n\n DB::shouldReceive('table')\n ->once()\n ->with('automated_reports')\n ->andReturn($queryBuilder);\n\n $queryBuilder->shouldReceive('join')\n ->once()\n ->with('teams', 'automated_reports.team_id', '=', 'teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('select')\n ->once()\n ->with('teams.id')\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('distinct')\n ->once()\n ->andReturnSelf();\n\n // 'where' call expected with team ID\n $queryBuilder->shouldReceive('where')\n ->once()\n ->with('teams.id', $teamId)\n ->andReturnSelf();\n\n $queryBuilder->shouldReceive('pluck')\n ->once()\n ->with('teams.id')\n ->andReturn($expectedCollection);\n\n // Execute\n $repository = new AutomatedReportsRepository();\n $result = $repository->getTeamIdsWithReportsResults($teamId);\n\n // Verify\n $this->assertSame($expectedCollection, $result);\n }\n\n public function testGetAllStandardReportsReturnsDelegatedCollection(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->with('created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports('created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAllStandardReportsDefaultParameters(): void\n {\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAllStandardReports'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAllStandardReports')\n ->willReturn($collection);\n\n $result = $repository->getAllStandardReports();\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_at', 'desc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserDefaultParameters(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user)\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user);\n\n $this->assertSame($collection, $result);\n }\n\n public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void\n {\n $user = $this->createMock(User::class);\n $collection = $this->createMock(Collection::class);\n\n $repository = $this->getMockBuilder(AutomatedReportsRepository::class)\n ->onlyMethods(['getAskJiminnyReportsByUser'])\n ->getMock();\n\n $repository->expects($this->once())\n ->method('getAskJiminnyReportsByUser')\n ->with($user, 'created_by', 'asc')\n ->willReturn($collection);\n\n $result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');\n\n $this->assertSame($collection, $result);\n }\n\n public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(7);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`groups`', $sql);\n\n $this->assertContains(100, $bindings);\n $this->assertContains(42, $bindings);\n $this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void\n {\n $user = $this->createMock(User::class);\n $user->method('getId')->willReturn(42);\n $user->method('getTeamId')->willReturn(100);\n $user->method('getGroupId')->willReturn(null);\n\n $query = AutomatedReport::query();\n\n $this->invokeApplyUserAccessScope($query, $user);\n\n $sql = $query->toSql();\n $bindings = $query->getBindings();\n\n $this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);\n $this->assertStringContainsString('`automated_reports`.`recipients`', $sql);\n $this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);\n $this->assertStringNotContainsString('`automated_reports`.`type`', $sql);\n $this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);\n }\n\n private function invokeApplyUserAccessScope(object $query, User $user): void\n {\n $repository = new AutomatedReportsRepository();\n $method = new ReflectionMethod($repository, 'applyUserAccessScope');\n $method->setAccessible(true);\n $method->invoke($repository, $query, $user);\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Execute","depth":4,"bounds":{"left":0.4956782,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Explain Plan","depth":4,"bounds":{"left":0.5043218,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse Query History","depth":4,"bounds":{"left":0.5152925,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"View Parameters","depth":4,"bounds":{"left":0.52393615,"top":0.14844373,"width":0.008643617,"height":0.01915403},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-3456631148116132766
|
-6069893671154377140
|
click
|
accessibility
|
NULL
|
Tests failed: 2, passed: 15, ignored: 4
text/html
Tests failed: 2, passed: 15, ignored: 4
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsRepositoryTest
Run 'AutomatedReportsRepositoryTest'
Debug 'AutomatedReportsRepositoryTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
3
5
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Repositories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Facades\DB;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Mockery;
use ReflectionMethod;
use Tests\TestCase;
class AutomatedReportsRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->withoutMockingConsoleOutput();
}
/**
* Test the update method.
*/
public function testUpdate(): void
{
// Create a mock of AutomatedReport
$reportMock = $this->createMock(AutomatedReport::class);
// Set up the update method to return true
$reportMock->expects($this->once())
->method('update')
->with(['type' => 'updated_type'])
->willReturn(true);
// Create the repository and call the update method
$repository = new AutomatedReportsRepository();
$result = $repository->update($reportMock, ['type' => 'updated_type']);
// Assert that the result is the report mock
$this->assertSame($reportMock, $result);
}
/**
* Test the create method by mocking the static create method.
*/
public function testCreate(): void
{
$data = ['team_id' => 1, 'type' => 'test_type'];
$report = $this->createMock(AutomatedReport::class);
// Use reflection to replace the create method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['create'])
->getMock();
$repository->expects($this->once())
->method('create')
->with($data)
->willReturn($report);
$result = $repository->create($data);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when a report is found.
*/
public function testFindByUuidWithExistingReport(): void
{
$uuid = 'test-uuid';
$report = $this->createMock(AutomatedReport::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn($report);
$result = $repository->findByUuid($uuid);
$this->assertSame($report, $result);
}
/**
* Test the findByUuid method when no report is found.
*/
public function testFindByUuidWithNonExistingReport(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findByUuid'])
->getMock();
$repository->expects($this->once())
->method('findByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findByUuid($uuid);
$this->assertNull($result);
}
public function testGetAllStandardReports(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
/**
* Test the createResult method.
*/
public function testCreateResult(): void
{
$data = ['report_id' => 1, 'status' => AutomatedReportResult::STATUS_REQUESTED];
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['createResult'])
->getMock();
$repository->expects($this->once())
->method('createResult')
->with($data)
->willReturn($reportResult);
$result = $repository->createResult($data);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when a result is found.
*/
public function testFindResultByUuidWithExistingResult(): void
{
$uuid = 'test-uuid';
$reportResult = $this->createMock(AutomatedReportResult::class);
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn($reportResult);
$result = $repository->findResultByUuid($uuid);
$this->assertSame($reportResult, $result);
}
/**
* Test the findResultByUuid method when no result is found.
*/
public function testFindResultByUuidWithNonExistingResult(): void
{
$uuid = 'non-existing-uuid';
// Use a partial mock of the repository to test the method
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['findResultByUuid'])
->getMock();
$repository->expects($this->once())
->method('findResultByUuid')
->with($uuid)
->willReturn(null);
$result = $repository->findResultByUuid($uuid);
$this->assertNull($result);
}
/**
* Test the getReportIdsByTeam method.
*/
public function testGetReportIdsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportsByTeam method.
*/
public function testGetReportsByTeam(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getResultsByReport method.
*/
public function testGetResultsByReport(): void
{
// Skip this test since it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of Eloquent static calls');
}
/**
* Test the getReportResultsQueryForRetention method.
*/
public function testGetReportResultsQueryForRetention(): void
{
// Skip this test for now - it requires more complex mocking
$this->markTestSkipped('This test requires more complex mocking of query builder');
}
/**
* Test the getTeamIdsWithReportsResults method without team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithoutFilter(): void
{
// Setup
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// No 'where' call expected
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults();
// Verify
$this->assertSame($expectedCollection, $result);
}
/**
* Test the getTeamIdsWithReportsResults method with team ID filter.
*/
public function testGetTeamIdsWithReportsResultsWithFilter(): void
{
// Setup
$teamId = 123;
$expectedCollection = Mockery::mock(SupportCollection::class);
// Mock DB facade
$queryBuilder = Mockery::mock('Illuminate\Database\Query\Builder');
DB::shouldReceive('table')
->once()
->with('automated_reports')
->andReturn($queryBuilder);
$queryBuilder->shouldReceive('join')
->once()
->with('teams', 'automated_reports.team_id', '=', 'teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('select')
->once()
->with('teams.id')
->andReturnSelf();
$queryBuilder->shouldReceive('distinct')
->once()
->andReturnSelf();
// 'where' call expected with team ID
$queryBuilder->shouldReceive('where')
->once()
->with('teams.id', $teamId)
->andReturnSelf();
$queryBuilder->shouldReceive('pluck')
->once()
->with('teams.id')
->andReturn($expectedCollection);
// Execute
$repository = new AutomatedReportsRepository();
$result = $repository->getTeamIdsWithReportsResults($teamId);
// Verify
$this->assertSame($expectedCollection, $result);
}
public function testGetAllStandardReportsReturnsDelegatedCollection(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->with('created_at', 'desc')
->willReturn($collection);
$result = $repository->getAllStandardReports('created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAllStandardReportsDefaultParameters(): void
{
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAllStandardReports'])
->getMock();
$repository->expects($this->once())
->method('getAllStandardReports')
->willReturn($collection);
$result = $repository->getAllStandardReports();
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserReturnsDelegatedCollection(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_at', 'desc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_at', 'desc');
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserDefaultParameters(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user)
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user);
$this->assertSame($collection, $result);
}
public function testGetAskJiminnyReportsByUserAcceptsCustomSort(): void
{
$user = $this->createMock(User::class);
$collection = $this->createMock(Collection::class);
$repository = $this->getMockBuilder(AutomatedReportsRepository::class)
->onlyMethods(['getAskJiminnyReportsByUser'])
->getMock();
$repository->expects($this->once())
->method('getAskJiminnyReportsByUser')
->with($user, 'created_by', 'asc')
->willReturn($collection);
$result = $repository->getAskJiminnyReportsByUser($user, 'created_by', 'asc');
$this->assertSame($collection, $result);
}
public function testApplyUserAccessScopeWithGroupIncludesAllBranches(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(7);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`type` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`groups`', $sql);
$this->assertContains(100, $bindings);
$this->assertContains(42, $bindings);
$this->assertContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
public function testApplyUserAccessScopeWithoutGroupOmitsGroupBranch(): void
{
$user = $this->createMock(User::class);
$user->method('getId')->willReturn(42);
$user->method('getTeamId')->willReturn(100);
$user->method('getGroupId')->willReturn(null);
$query = AutomatedReport::query();
$this->invokeApplyUserAccessScope($query, $user);
$sql = $query->toSql();
$bindings = $query->getBindings();
$this->assertStringContainsString('`automated_reports`.`team_id` = ?', $sql);
$this->assertStringContainsString('`automated_reports`.`recipients`', $sql);
$this->assertStringContainsString('`automated_reports`.`created_by` = ?', $sql);
$this->assertStringNotContainsString('`automated_reports`.`groups`', $sql);
$this->assertStringNotContainsString('`automated_reports`.`type`', $sql);
$this->assertNotContains(AutomatedReportsService::TYPE_ASK_JIMINNY, $bindings);
}
private function invokeApplyUserAccessScope(object $query, User $user): void
{
$repository = new AutomatedReportsRepository();
$method = new ReflectionMethod($repository, 'applyUserAccessScope');
$method->setAccessible(true);
$method->invoke($repository, $query, $user);
}
}
Execute
Explain Plan
Browse Query History
View Parameters...
|
65964
|
|
11810
|
242
|
15
|
2026-04-14T10:13:16.753563+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776161596753_m1.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsCommandTest.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 10, passed: 0
text/html
text/html
te Tests failed: 10, passed: 0
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
248
Previous Highlighted Error
Next Highlighted Error
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {"activity_id":407307} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:00","to":"10:05"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"23:55","to":"00:00"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-14T10:07:34.196472Z"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring start {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring end {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":1} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":56,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":169.9,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.75} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring start {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring end {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 0 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"df4d1442-bcd6-4b63-8513-802ae90993e6","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring start {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring end {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring start {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring end {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"c7aba065-c8f1-473d-b8b5-4797245873bf","trace_id":"48f587f9-dd77-4634-9ad9-1137b029b5f5"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring start {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring end {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:05","to":"10:10"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"00:00","to":"00:05"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda11...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 10, passed: 0","depth":3,"value":"Tests failed: 10, passed: 0","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"248","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {\"activity_id\":407307} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:00\",\"to\":\"10:05\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"23:55\",\"to\":\"00:00\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:07:34.196472Z\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring start {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring end {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":1} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":169.9,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring start {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring end {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"df4d1442-bcd6-4b63-8513-802ae90993e6\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring start {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring end {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring start {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring end {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {\"team_id\":1,\"reason\":\"{\\\"message\\\":\\\"Forbidden\\\"}\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {\"options\":{\"from\":null,\"to\":null,\"help\":false,\"silent\":false,\"quiet\":false,\"verbose\":false,\"version\":false,\"ansi\":null,\"no-interaction\":false,\"env\":null}} {\"correlation_id\":\"c7aba065-c8f1-473d-b8b5-4797245873bf\",\"trace_id\":\"48f587f9-dd77-4634-9ad9-1137b029b5f5\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring start {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring end {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:05\",\"to\":\"10:10\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:00\",\"to\":\"00:05\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:12:30.407181Z\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:41] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring start {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring end {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":252.2,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring start {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring end {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:27] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"ad382f07-8ff8-450f-b38c-fba577285d06\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring start {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring end {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}","depth":4,"value":"[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {\"activity_id\":407307} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:00\",\"to\":\"10:05\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"23:55\",\"to\":\"00:00\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:07:34.196472Z\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring start {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring end {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":1} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":169.9,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring start {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring end {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"df4d1442-bcd6-4b63-8513-802ae90993e6\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring start {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring end {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring start {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring end {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {\"team_id\":1,\"reason\":\"{\\\"message\\\":\\\"Forbidden\\\"}\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {\"options\":{\"from\":null,\"to\":null,\"help\":false,\"silent\":false,\"quiet\":false,\"verbose\":false,\"version\":false,\"ansi\":null,\"no-interaction\":false,\"env\":null}} {\"correlation_id\":\"c7aba065-c8f1-473d-b8b5-4797245873bf\",\"trace_id\":\"48f587f9-dd77-4634-9ad9-1137b029b5f5\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring start {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring end {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:05\",\"to\":\"10:10\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:00\",\"to\":\"00:05\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:12:30.407181Z\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:41] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring start {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring end {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":252.2,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring start {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring end {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:27] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"ad382f07-8ff8-450f-b38c-fba577285d06\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring start {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring end {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"12","depth":4,"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Console\\Commands\\Reports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Jiminny\\Console\\Commands\\Reports\\AutomatedReportsCommand;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateReportJob;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AutomatedReportsCommandTest extends TestCase\n{\n private LoggerInterface&Mockery\\MockInterface $logger;\n private Dispatcher&Mockery\\MockInterface $dispatcher;\n private AutomatedReportsRepository&Mockery\\MockInterface $reportRepository;\n private AutomatedReportsCommand $command;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->logger = Mockery::mock(LoggerInterface::class);\n $this->dispatcher = Mockery::mock(Dispatcher::class);\n $this->reportRepository = Mockery::mock(AutomatedReportsRepository::class);\n $this->command = new AutomatedReportsCommand($this->logger, $this->dispatcher, $this->reportRepository);\n }\n\n protected function tearDown(): void\n {\n Carbon::setTestNow();\n Mockery::close();\n parent::tearDown();\n }\n\n public function testProcessDailyReportsEveryDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $reports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($reports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDispatchesAskJiminnyJobForAskJiminnyReports(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $askJiminnyReport = $this->createAskJiminnyReport(AutomatedReportsService::FREQUENCY_DAILY);\n $standardReport = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1)[0];\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([$askJiminnyReport, $standardReport]));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateAskJiminnyReportJob::class));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessWeeklyReportsOnMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 11, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(3)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessWeeklyReportsOnNonMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 12, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessMonthlyReportsOnFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessMonthlyReportsOnNonFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY);\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessQuarterlyReportsOnFirstDayOfQuarterlyMonth(): void\n {\n // 2024-10-01 is a Tuesday (first day of quarterly month, not Monday)\n Carbon::setTestNow(Carbon::create(2024, 10, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessQuarterlyReportsOnNonQuarterlyFirstDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessAllFrequenciesOnMondayFirstDayOfQuarterlyMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 7, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 1);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(4)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testReturnsZeroOnSuccess(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([]));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n private function createStandardReports(string $frequency, int $count): array\n {\n $reports = [];\n\n for ($i = 0; $i < $count; $i++) {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('uuid-' . $i);\n $report->shouldReceive('getTeamId')->andReturn($i + 1);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_LOSS_ANALYSIS);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(false);\n\n $reports[] = $report;\n }\n\n return $reports;\n }\n\n private function createAskJiminnyReport(string $frequency): mixed\n {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('ask-jiminny-uuid');\n $report->shouldReceive('getTeamId')->andReturn(99);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(true);\n\n return $report;\n }\n}","depth":4,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Console\\Commands\\Reports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Jiminny\\Console\\Commands\\Reports\\AutomatedReportsCommand;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateReportJob;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AutomatedReportsCommandTest extends TestCase\n{\n private LoggerInterface&Mockery\\MockInterface $logger;\n private Dispatcher&Mockery\\MockInterface $dispatcher;\n private AutomatedReportsRepository&Mockery\\MockInterface $reportRepository;\n private AutomatedReportsCommand $command;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->logger = Mockery::mock(LoggerInterface::class);\n $this->dispatcher = Mockery::mock(Dispatcher::class);\n $this->reportRepository = Mockery::mock(AutomatedReportsRepository::class);\n $this->command = new AutomatedReportsCommand($this->logger, $this->dispatcher, $this->reportRepository);\n }\n\n protected function tearDown(): void\n {\n Carbon::setTestNow();\n Mockery::close();\n parent::tearDown();\n }\n\n public function testProcessDailyReportsEveryDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $reports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($reports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDispatchesAskJiminnyJobForAskJiminnyReports(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $askJiminnyReport = $this->createAskJiminnyReport(AutomatedReportsService::FREQUENCY_DAILY);\n $standardReport = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1)[0];\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([$askJiminnyReport, $standardReport]));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateAskJiminnyReportJob::class));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessWeeklyReportsOnMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 11, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(3)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessWeeklyReportsOnNonMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 12, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessMonthlyReportsOnFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessMonthlyReportsOnNonFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY);\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessQuarterlyReportsOnFirstDayOfQuarterlyMonth(): void\n {\n // 2024-10-01 is a Tuesday (first day of quarterly month, not Monday)\n Carbon::setTestNow(Carbon::create(2024, 10, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessQuarterlyReportsOnNonQuarterlyFirstDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessAllFrequenciesOnMondayFirstDayOfQuarterlyMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 7, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 1);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(4)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testReturnsZeroOnSuccess(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([]));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n private function createStandardReports(string $frequency, int $count): array\n {\n $reports = [];\n\n for ($i = 0; $i < $count; $i++) {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('uuid-' . $i);\n $report->shouldReceive('getTeamId')->andReturn($i + 1);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_LOSS_ANALYSIS);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(false);\n\n $reports[] = $report;\n }\n\n return $reports;\n }\n\n private function createAskJiminnyReport(string $frequency): mixed\n {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('ask-jiminny-uuid');\n $report->shouldReceive('getTeamId')->andReturn(99);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(true);\n\n return $report;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7108548186427266741
|
-2358355934385201923
|
idle
|
accessibility
|
NULL
|
Tests failed: 10, passed: 0
text/html
text/html
te Tests failed: 10, passed: 0
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
248
Previous Highlighted Error
Next Highlighted Error
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {"activity_id":407307} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:00","to":"10:05"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"23:55","to":"00:00"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-14T10:07:34.196472Z"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring start {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring end {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":1} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":56,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":169.9,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.75} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring start {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring end {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 0 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"df4d1442-bcd6-4b63-8513-802ae90993e6","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring start {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring end {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring start {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring end {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"c7aba065-c8f1-473d-b8b5-4797245873bf","trace_id":"48f587f9-dd77-4634-9ad9-1137b029b5f5"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring start {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring end {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:05","to":"10:10"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"00:00","to":"00:05"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda11...
|
11808
|
|
11811
|
243
|
28
|
2026-04-14T10:13:17.458153+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-14/1776 /Users/lukas/.screenpipe/data/data/2026-04-14/1776161597458_m2.jpg...
|
PhpStorm
|
faVsco.js – AutomatedReportsCommandTest.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Tests failed: 10, passed: 0
text/html
text/html
te Tests failed: 10, passed: 0
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
248
Previous Highlighted Error
Next Highlighted Error
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {"activity_id":407307} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:00","to":"10:05"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"23:55","to":"00:00"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-14T10:07:34.196472Z"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring start {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring end {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":1} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":56,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":169.9,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.75} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring start {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring end {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 0 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"df4d1442-bcd6-4b63-8513-802ae90993e6","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring start {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring end {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring start {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring end {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"c7aba065-c8f1-473d-b8b5-4797245873bf","trace_id":"48f587f9-dd77-4634-9ad9-1137b029b5f5"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring start {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring end {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:05","to":"10:10"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"00:00","to":"00:05"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda11...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Tests failed: 10, passed: 0","depth":3,"bounds":{"left":0.9175781,"top":0.11180556,"width":0.0625,"height":0.011805556},"value":"Tests failed: 10, passed: 0","help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"bounds":{"left":0.9175781,"top":0.11180556,"width":0.0625,"height":0.011805556},"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"text/html","depth":4,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.03046875,"top":0.017361112,"width":0.0453125,"height":0.022222223},"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"#11894 on JY-18909-automated-reports-ask-jiminny, menu","depth":5,"bounds":{"left":0.07578125,"top":0.017361112,"width":0.14257812,"height":0.022222223},"help_text":"Pull request #11894 exists for current branch JY-18909-automated-reports-ask-jiminny","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.78515625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AutomatedReportsCommandTest","depth":6,"bounds":{"left":0.803125,"top":0.017361112,"width":0.09765625,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9007813,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AutomatedReportsCommandTest'","depth":6,"bounds":{"left":0.9140625,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9273437,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96015626,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9734375,"top":0.017361112,"width":0.01328125,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9867188,"top":0.017361112,"width":0.013281226,"height":0.022222223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"248","depth":4,"bounds":{"left":0.58085936,"top":0.15208334,"width":0.01484375,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.59765625,"top":0.15069444,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.60625,"top":0.15069444,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {\"activity_id\":407307} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:00\",\"to\":\"10:05\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"23:55\",\"to\":\"00:00\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:07:34.196472Z\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring start {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring end {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":1} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":169.9,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring start {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring end {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"df4d1442-bcd6-4b63-8513-802ae90993e6\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring start {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring end {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring start {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring end {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {\"team_id\":1,\"reason\":\"{\\\"message\\\":\\\"Forbidden\\\"}\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {\"options\":{\"from\":null,\"to\":null,\"help\":false,\"silent\":false,\"quiet\":false,\"verbose\":false,\"version\":false,\"ansi\":null,\"no-interaction\":false,\"env\":null}} {\"correlation_id\":\"c7aba065-c8f1-473d-b8b5-4797245873bf\",\"trace_id\":\"48f587f9-dd77-4634-9ad9-1137b029b5f5\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring start {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring end {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:05\",\"to\":\"10:10\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:00\",\"to\":\"00:05\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:12:30.407181Z\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:41] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring start {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring end {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":252.2,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring start {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring end {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:27] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"ad382f07-8ff8-450f-b38c-fba577285d06\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring start {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring end {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}","depth":4,"bounds":{"left":0.3859375,"top":0.14930555,"width":0.6140625,"height":0.8506944},"value":"[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3\",\"trace_id\":\"9e5fa115-9cf5-45d6-a1da-109e154cfae7\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d9d68876-323b-42bd-9953-635da8008f2e\",\"trace_id\":\"6c00ce4c-f2df-4ccb-908d-4862de973096\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {\"activity_id\":407307} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:26] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"27d73fea-a8cb-4757-9bbb-fbe7222e6943\",\"trace_id\":\"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:00\",\"to\":\"10:05\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"23:55\",\"to\":\"00:00\"} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:28] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"588bae45-3998-4b86-b056-51aaf8b6882f\",\"trace_id\":\"8f716a65-7768-44c5-b0e4-02a859f329e5\"}\n[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"da835e46-68f6-4d4c-bf42-aa41de2e599d\",\"trace_id\":\"7d96dad0-2325-4190-bff6-a2d83f01c4e1\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"73b127c9-c033-4fa7-a8ed-2e28077ba970\",\"trace_id\":\"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:07:34.196472Z\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec\",\"trace_id\":\"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd\"}\n[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a\",\"trace_id\":\"b4c87c8d-120d-48fb-b13c-aee84323f3e2\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8403c1e4-b5b6-41f9-9280-ff666beed28b\",\"trace_id\":\"3f631f60-6d14-44f5-b6c3-b401999867f9\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring start {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:08] local.NOTICE: Monitoring end {\"correlation_id\":\"0d51eb08-f2f1-4322-97d0-268700856a99\",\"trace_id\":\"92b45b9a-b6e8-40c8-a783-193cf1a4eea2\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"0566c59b-bf67-49eb-970a-a6b955ddd756\",\"trace_id\":\"724c5f5e-cd88-41fb-9b7e-74315e131024\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"33d190d4-0abb-4e63-af52-95faa913e7a1\",\"trace_id\":\"703aaa4d-a551-4ed1-8a4a-609d373e2fc0\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"da41d77d-46d9-41ae-90b7-f8418b05fb9a\",\"trace_id\":\"47329a55-e6f4-406b-8730-e72d69737f09\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:notify-not-logged\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"310e7e99-6de4-4f93-967e-37c78e9ec826\",\"trace_id\":\"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {\"host\":\"docker_lamp_1\",\"events\":1} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:19] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"de8430ad-f242-4536-bebe-e165320b6b26\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {\"inbox_id\":59} {\"correlation_id\":\"eb46c86e-5c80-48b1-a4ee-ac425f197754\",\"trace_id\":\"75f83d2f-4e5e-4086-b062-7c1d24b5b74c\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":169.9,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"8dfaefe8-60b1-4846-bfa3-eb703c71deb8\",\"trace_id\":\"186e2ff6-96dc-4ae6-a31b-0c3849678f7b\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"53329974-bdd6-4cd2-a022-0d565b0a5cf4\",\"trace_id\":\"08246763-f961-45fb-b262-51378bdbc955\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2b6586f0-419e-4422-8800-e4b7619c43d6\",\"trace_id\":\"221c682d-c45f-4e4a-a793-ed11006dc6a1\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring start {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:08] local.NOTICE: Monitoring end {\"correlation_id\":\"9ba692ee-5286-41ca-abf0-86f6e1167a8e\",\"trace_id\":\"d58ab8ad-7737-4d7c-91c0-81733c21e709\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"28e708ed-74ee-4417-a8da-c1a9ae0cd745\",\"trace_id\":\"bd848201-9700-40bf-8fa4-9df13ae88f6e\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"95326c5a-7b17-475d-9245-634d15d51bfe\",\"trace_id\":\"31b36f9d-93cc-4917-bc7b-6b6a3da139e7\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"f4f68165-0bcc-4e79-89e5-81f9c627e4dc\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:07:15] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"a7d5580f-8b90-4ff6-91e8-de94514f8ca1\",\"trace_id\":\"fc40909d-86c1-4e45-bd51-96e2bd8c4556\"}\n[2026-04-14 10:07:16] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"df4d1442-bcd6-4b63-8513-802ae90993e6\",\"trace_id\":\"5766aab2-0316-400f-aa89-168ea0579941\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"43299ccb-af86-42b1-b793-4ede57e2b91d\",\"trace_id\":\"2f919b69-9d68-488c-89fa-2aade6ef8519\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cde80757-333e-4268-a902-892c031b2373\",\"trace_id\":\"36e932e0-8362-432f-9e36-d819f902d49e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring start {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:07] local.NOTICE: Monitoring end {\"correlation_id\":\"146390f8-e91a-4e5c-b282-0c83b4947101\",\"trace_id\":\"fb689e40-c867-4fc5-9847-c223835da80e\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"58e23da5-8f3f-4836-aac7-315ab32f74a3\",\"trace_id\":\"c71267c4-b5fe-42ad-9bcd-76f6299c1538\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"9158bb6f-c165-495a-9b6e-8077d8343c87\",\"trace_id\":\"df378af8-cacb-41ea-b030-b8f53144fddd\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"35774df7-e046-49ec-835f-45f9dc1ec025\",\"trace_id\":\"45c5e102-6b65-4d23-9edd-684ca996316c\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:08:17] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d4dc4b3b-8dc8-4665-aec7-139be0df2306\",\"trace_id\":\"853f2210-e804-40aa-a1d8-4457c60f4993\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:05] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8052fb2f-5876-4e2c-8f64-c770d9fe37fe\",\"trace_id\":\"63249c19-bf59-4e52-9096-156d537a9786\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1dd274b4-f71d-486e-b6c4-aaf106304102\",\"trace_id\":\"1d55139c-a58d-4456-801a-9bcb3bf9b024\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring start {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:10] local.NOTICE: Monitoring end {\"correlation_id\":\"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad\",\"trace_id\":\"8e60392d-4ea4-41f2-be2f-a01853c7a1ab\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:13] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"2de9f84e-2691-459d-adfd-f5f6746501d0\",\"trace_id\":\"418ad958-86c0-4477-b0fb-e487768d6fa1\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"1be8488c-ae59-4f42-8475-c426905c3ca6\",\"trace_id\":\"aeb8b412-635e-4929-915f-fb3fe5786eae\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {\"socialAccountId\":1496,\"provider\":\"aircall\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {\"team_id\":1,\"reason\":\"{\\\"message\\\":\\\"Forbidden\\\"}\"} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:aircall:check-and-renew\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b3d1b688-309f-4df0-9dcd-9a733c87f597\",\"trace_id\":\"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c\"}\n[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {\"options\":{\"from\":null,\"to\":null,\"help\":false,\"silent\":false,\"quiet\":false,\"verbose\":false,\"version\":false,\"ansi\":null,\"no-interaction\":false,\"env\":null}} {\"correlation_id\":\"c7aba065-c8f1-473d-b8b5-4797245873bf\",\"trace_id\":\"48f587f9-dd77-4634-9ad9-1137b029b5f5\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7310aae5-3829-4d5c-bbc9-f6550a35f539\",\"trace_id\":\"6099cf10-45e2-4b82-9649-649e2f8a1209\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5119d94e-0c61-4312-a740-2b569388cc9f\",\"trace_id\":\"6a412a32-3bc3-46a4-af7c-24ad560a4e41\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring start {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:08] local.NOTICE: Monitoring end {\"correlation_id\":\"95cdb3b7-b356-4325-be28-20663ece6aa0\",\"trace_id\":\"e64a75a4-2493-4f35-ba46-ccd002e6b7b7\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"6460dd5f-4b94-4cd1-867e-53304d785744\",\"trace_id\":\"683b103d-2dfc-4266-84a2-d5cd799b133f\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"5a59007d-2a12-4977-8a5a-59c678aa12ee\",\"trace_id\":\"bc3a2ad5-9f13-4728-adaa-529da75cf011\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:14] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"afe2ff6b-1b9e-4485-bdad-a758d873a782\",\"trace_id\":\"d507becf-55d6-4fda-8831-e981f95b85c8\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"activity:purge-stale\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0\",\"trace_id\":\"05b44216-d5ac-44cd-b74b-3e92567b192d\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:18] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:text-relay:sync\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d76b00ee-6271-4df7-ba45-769738bbb7ae\",\"trace_id\":\"92384790-9588-452d-abea-cdd25a2fe7ff\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-notification\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"c8f3b5c1-bef5-4584-b9d8-0c10578fefac\",\"trace_id\":\"b700f03d-b02b-495e-b505-1598c7a9ea4b\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:start\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d7b97fbb-00b9-47c2-ae81-dda03fd8b253\",\"trace_id\":\"54447306-49bf-4629-8de0-bb191c61cce2\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesEnded {\"from\":\"10:05\",\"to\":\"10:10\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\\Console\\Commands\\Activities\\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {\"from\":\"00:00\",\"to\":\"00:05\"} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:23] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:end\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"07665538-664f-421e-af9b-97152a688b68\",\"trace_id\":\"d116813e-2d96-4a3d-ae04-6e53d69ca565\"}\n[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":59,\"provider\":\"hubspot\",\"refreshToken\":\"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":59,\"updated_at\":\"2025-10-03 09:32:05\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":306,\"provider\":\"hubspot\",\"refreshToken\":\"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.ERROR: Failed to refresh HubSpot token {\"account_id\":306,\"updated_at\":\"2023-11-27 09:30:03\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: Trying to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [EncryptedTokenManager] Generating access token. {\"mode\":\"legacy\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:26] local.INFO: [SocialAccountService] Refreshing token from provider {\"socialAccountId\":1372,\"provider\":\"hubspot\",\"refreshToken\":\"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4\",\"state\":\"full-refresh\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.ERROR: Failed to refresh HubSpot token {\"account_id\":1372,\"updated_at\":\"2025-10-02 14:47:06\",\"reason\":\"missing or invalid refresh token\",\"previous\":\"\"} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:27] local.NOTICE: Repairing HubSpot tokens end {\"total\":3,\"fixed\":0,\"failed\":3} {\"correlation_id\":\"d269668d-cb18-4aeb-9a05-13a9e53195ed\",\"trace_id\":\"ecbe2fde-809f-4720-8113-609eda111b53\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"jiminny:transcription:retry-failed\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"8d29e53b-638b-4e55-8ee6-70567699b366\",\"trace_id\":\"ac17c380-0e7e-4425-bbc9-9e292ed99ea5\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Command] Starting polling service {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Service starting {\"memory_limit\":\"256M\",\"max_execution_time\":\"0\",\"initial_memory_mb\":62.0} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Acquired polling lock {\"expires_at\":\"2026-04-14T10:12:30.407181Z\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:30] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:pre-meeting-reminder\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"cba13635-8dbf-4423-ad70-69f2d927c6ff\",\"trace_id\":\"cca585f7-e8b4-4cbd-b63f-8d9be90960a6\"}\n[2026-04-14 10:10:30] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:32] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:reset-governor\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4943604a-02ac-42cd-82ee-524a684c9d07\",\"trace_id\":\"dea56e3f-9697-4054-8b54-7db752a8613f\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:34] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"crm:bullhorn:ping\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"7ae1dc8e-0ce6-461b-a054-be59c56de0b5\",\"trace_id\":\"8892af7b-37c9-4892-82fd-aa4596edc512\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:35] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:40] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:41] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:10:56] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"79c8dc9b-058f-4f0e-9e9a-2e34a66209a7\",\"trace_id\":\"c6894787-c096-4c96-96cf-8ed81e40aa59\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"d52d55c7-74a3-409b-b458-fecfd8990f5a\",\"trace_id\":\"407ded8c-1b50-4ba7-8190-f3e84814124e\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring start {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:08] local.NOTICE: Monitoring end {\"correlation_id\":\"296fc7a0-329d-43bf-8c98-547c407c447c\",\"trace_id\":\"d2bbc8a9-18cb-408f-98a9-f87d6a2e4cb9\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:10] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4aaf62d1-0d46-4f16-af0e-0617eb460382\",\"trace_id\":\"7bcc5249-9d16-44e5-a83c-3a613e00c860\"}\n[2026-04-14 10:11:11] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:12] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"19290448-0263-4849-93fc-f09885da0c1f\",\"trace_id\":\"1483761c-464c-4374-994f-121758fcd534\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Getting offset from database {\"offset\":\"\",\"jiminny_team_id\":1} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal API] Fetching latest journal entry {\"url\":\"https://api.hubapi.com/webhooks/v4/journal/latest\"} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] No data {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {\"empty_results\":5,\"max_empty_results\":5} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Service ending {\"runtime_seconds\":56,\"total_cycles\":5,\"files_downloaded\":0,\"empty_files\":0,\"other_portal_skipped\":0,\"total_events\":0,\"events_per_file\":0,\"avg_api_ms\":252.2,\"avg_download_ms\":0.0,\"avg_transform_ms\":0.0,\"avg_process_ms\":0.0,\"peak_memory_mb\":99.75} {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:11:26] local.INFO: [HubSpot Journal Polling] Released polling lock {\"correlation_id\":\"f896bee6-1a5b-4790-8dd6-af38522db495\",\"trace_id\":\"6ba6c565-e21f-41ef-9f42-301c78a24f31\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:07] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"74ad5a14-81b2-48f4-bb37-2d2ac73c5059\",\"trace_id\":\"6d6d809e-3d77-400c-a351-ac4db72cf3fc\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:09] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3671d2e5-29f6-43db-8f1a-4a0bf5ff669b\",\"trace_id\":\"1295351d-9c37-47db-bad6-dbccb4c6466f\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring start {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:11] local.NOTICE: Monitoring end {\"correlation_id\":\"f2d467c8-e822-4691-9920-cac071f14877\",\"trace_id\":\"4d67db65-374a-4eb2-9cc6-8c60dc9ebabc\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:16] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:skip-lists:refresh\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"04d8a469-317c-40e1-a163-ad747408f9e3\",\"trace_id\":\"bafa4d05-7e55-4524-b0a5-1764ecb964fe\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] STARTING batch process {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: [EmailSchedule] FINISHED batch process {\"host\":\"docker_lamp_1\",\"processed\":0} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:20] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:process\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"3cd3a658-bd65-4253-9c8d-b94cdd2f3c4d\",\"trace_id\":\"5ae25493-7098-481f-84f4-225c6595b140\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:10:00, 2026-04-14 10:12:00] {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:22] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"conference:monitor:count\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"4cabe4c8-81a7-4405-958c-783cc42dc31a\",\"trace_id\":\"844b27d2-1727-4f1a-bb9a-a3ad44d9d37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] STARTING batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: [EmailSchedule] FINISHED batch create {\"host\":\"docker_lamp_1\"} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:25] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"mailbox:batch:create\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"b573f9e3-5771-4b03-9fd9-1ff7d571916d\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:12:27] local.INFO: [Jiminny\\Jobs\\Mailbox\\CreateBatches] processed 0 inboxes and created 0 batches {\"userId\":null,\"batchSize\":30,\"maxBatches\":1000} {\"correlation_id\":\"ad382f07-8ff8-450f-b38c-fba577285d06\",\"trace_id\":\"e76af6ed-1fff-4aef-8b17-a8288121b37a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:04] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"meeting-bot:schedule-bot\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"24a30c58-6ce4-427b-972e-87df2b1f0d31\",\"trace_id\":\"30809930-a6a8-49bb-9c61-f4dcca23683a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage before starting command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryPeakBeforeCommandInMb\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:06] local.INFO: Jiminny\\Console\\Commands\\Command::run Memory usage for command {\"command\":\"dialers:monitor-activities\",\"memoryBeforeCommandInMb\":62.0,\"memoryAfterCommandInMB\":62.0,\"memoryPeakBeforeCommandInMb\":99.746,\"memoryPeakAfterCommandInMB\":99.746} {\"correlation_id\":\"db368cd5-b4ea-4f77-8169-24ff5403199d\",\"trace_id\":\"0bd7332d-c4c1-44b6-b204-e68b8ec6217a\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring start {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}\n[2026-04-14 10:13:08] local.NOTICE: Monitoring end {\"correlation_id\":\"98628e85-9d56-4088-8ddb-5a33768305d6\",\"trace_id\":\"138288e6-6342-4250-9c1e-46921c407423\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.049609374,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"10","depth":4,"bounds":{"left":0.30507812,"top":0.23819445,"width":0.011328125,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.31875,"top":0.23819445,"width":0.011328125,"height":0.013194445},"role_description":"text"},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.33242187,"top":0.23819445,"width":0.009375,"height":0.013194445},"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.34375,"top":0.23680556,"width":0.00859375,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.35234374,"top":0.23680556,"width":0.008203125,"height":0.015972223},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Console\\Commands\\Reports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Jiminny\\Console\\Commands\\Reports\\AutomatedReportsCommand;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateReportJob;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AutomatedReportsCommandTest extends TestCase\n{\n private LoggerInterface&Mockery\\MockInterface $logger;\n private Dispatcher&Mockery\\MockInterface $dispatcher;\n private AutomatedReportsRepository&Mockery\\MockInterface $reportRepository;\n private AutomatedReportsCommand $command;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->logger = Mockery::mock(LoggerInterface::class);\n $this->dispatcher = Mockery::mock(Dispatcher::class);\n $this->reportRepository = Mockery::mock(AutomatedReportsRepository::class);\n $this->command = new AutomatedReportsCommand($this->logger, $this->dispatcher, $this->reportRepository);\n }\n\n protected function tearDown(): void\n {\n Carbon::setTestNow();\n Mockery::close();\n parent::tearDown();\n }\n\n public function testProcessDailyReportsEveryDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $reports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($reports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDispatchesAskJiminnyJobForAskJiminnyReports(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $askJiminnyReport = $this->createAskJiminnyReport(AutomatedReportsService::FREQUENCY_DAILY);\n $standardReport = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1)[0];\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([$askJiminnyReport, $standardReport]));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateAskJiminnyReportJob::class));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessWeeklyReportsOnMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 11, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(3)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessWeeklyReportsOnNonMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 12, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessMonthlyReportsOnFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessMonthlyReportsOnNonFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY);\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessQuarterlyReportsOnFirstDayOfQuarterlyMonth(): void\n {\n // 2024-10-01 is a Tuesday (first day of quarterly month, not Monday)\n Carbon::setTestNow(Carbon::create(2024, 10, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessQuarterlyReportsOnNonQuarterlyFirstDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessAllFrequenciesOnMondayFirstDayOfQuarterlyMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 7, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 1);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(4)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testReturnsZeroOnSuccess(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([]));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n private function createStandardReports(string $frequency, int $count): array\n {\n $reports = [];\n\n for ($i = 0; $i < $count; $i++) {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('uuid-' . $i);\n $report->shouldReceive('getTeamId')->andReturn($i + 1);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_LOSS_ANALYSIS);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(false);\n\n $reports[] = $report;\n }\n\n return $reports;\n }\n\n private function createAskJiminnyReport(string $frequency): mixed\n {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('ask-jiminny-uuid');\n $report->shouldReceive('getTeamId')->andReturn(99);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(true);\n\n return $report;\n }\n}","depth":4,"bounds":{"left":0.15234375,"top":0.23541667,"width":0.2828125,"height":0.76458335},"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Tests\\Unit\\Console\\Commands\\Reports;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Contracts\\Bus\\Dispatcher;\nuse Illuminate\\Database\\Eloquent\\Collection;\nuse Jiminny\\Console\\Commands\\Reports\\AutomatedReportsCommand;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateAskJiminnyReportJob;\nuse Jiminny\\Jobs\\AutomatedReports\\RequestGenerateReportJob;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Mockery;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Log\\LoggerInterface;\n\nclass AutomatedReportsCommandTest extends TestCase\n{\n private LoggerInterface&Mockery\\MockInterface $logger;\n private Dispatcher&Mockery\\MockInterface $dispatcher;\n private AutomatedReportsRepository&Mockery\\MockInterface $reportRepository;\n private AutomatedReportsCommand $command;\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->logger = Mockery::mock(LoggerInterface::class);\n $this->dispatcher = Mockery::mock(Dispatcher::class);\n $this->reportRepository = Mockery::mock(AutomatedReportsRepository::class);\n $this->command = new AutomatedReportsCommand($this->logger, $this->dispatcher, $this->reportRepository);\n }\n\n protected function tearDown(): void\n {\n Carbon::setTestNow();\n Mockery::close();\n parent::tearDown();\n }\n\n public function testProcessDailyReportsEveryDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $reports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($reports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDispatchesAskJiminnyJobForAskJiminnyReports(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $askJiminnyReport = $this->createAskJiminnyReport(AutomatedReportsService::FREQUENCY_DAILY);\n $standardReport = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1)[0];\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([$askJiminnyReport, $standardReport]));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateAskJiminnyReportJob::class));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->once()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessWeeklyReportsOnMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 11, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(3)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessWeeklyReportsOnNonMonday(): void\n {\n Carbon::setTestNow(Carbon::create(2023, 12, 12, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessMonthlyReportsOnFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 2);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessMonthlyReportsOnNonFirstDayOfMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY);\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessQuarterlyReportsOnFirstDayOfQuarterlyMonth(): void\n {\n // 2024-10-01 is a Tuesday (first day of quarterly month, not Monday)\n Carbon::setTestNow(Carbon::create(2024, 10, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->twice()\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testDoNotProcessQuarterlyReportsOnNonQuarterlyFirstDay(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 0);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 0);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldNotReceive('getActiveReportsByFrequency')\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY);\n\n $this->dispatcher->shouldNotReceive('dispatch');\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testProcessAllFrequenciesOnMondayFirstDayOfQuarterlyMonth(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 7, 1, 10, 0, 0));\n\n $dailyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_DAILY, 1);\n $weeklyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_WEEKLY, 1);\n $monthlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_MONTHLY, 1);\n $quarterlyReports = $this->createStandardReports(AutomatedReportsService::FREQUENCY_QUARTERLY, 1);\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection($dailyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_WEEKLY)\n ->andReturn(new Collection($weeklyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_MONTHLY)\n ->andReturn(new Collection($monthlyReports));\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_QUARTERLY)\n ->andReturn(new Collection($quarterlyReports));\n\n $this->dispatcher->shouldReceive('dispatch')\n ->times(4)\n ->with(Mockery::type(RequestGenerateReportJob::class));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n public function testReturnsZeroOnSuccess(): void\n {\n Carbon::setTestNow(Carbon::create(2024, 3, 13, 10, 0, 0));\n\n $this->logger->shouldReceive('info')->atLeast()->once();\n\n $this->reportRepository->shouldReceive('getActiveReportsByFrequency')\n ->once()\n ->with(AutomatedReportsService::FREQUENCY_DAILY)\n ->andReturn(new Collection([]));\n\n $result = $this->command->handle();\n\n $this->assertEquals(0, $result);\n }\n\n private function createStandardReports(string $frequency, int $count): array\n {\n $reports = [];\n\n for ($i = 0; $i < $count; $i++) {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('uuid-' . $i);\n $report->shouldReceive('getTeamId')->andReturn($i + 1);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_LOSS_ANALYSIS);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(false);\n\n $reports[] = $report;\n }\n\n return $reports;\n }\n\n private function createAskJiminnyReport(string $frequency): mixed\n {\n $report = Mockery::mock();\n $report->shouldReceive('getUuid')->andReturn('ask-jiminny-uuid');\n $report->shouldReceive('getTeamId')->andReturn(99);\n $report->shouldReceive('getFrequency')->andReturn($frequency);\n $report->shouldReceive('getType')->andReturn(AutomatedReportsService::TYPE_ASK_JIMINNY);\n $report->shouldReceive('isAskJiminnyReport')->andReturn(true);\n\n return $report;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.0140625,"top":0.041666668,"width":0.028515626,"height":0.021527778},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.23320313,"top":1.0,"width":0.01015625,"height":0.0},"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7108548186427266741
|
-2358355934385201923
|
idle
|
accessibility
|
NULL
|
Tests failed: 10, passed: 0
text/html
text/html
te Tests failed: 10, passed: 0
text/html
text/html
text/html
Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsCommandTest
Run 'AutomatedReportsCommandTest'
Debug 'AutomatedReportsCommandTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
248
Previous Highlighted Error
Next Highlighted Error
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9756bfa7-c8e1-4b17-bc55-9f224d2af2e3","trace_id":"9e5fa115-9cf5-45d6-a1da-109e154cfae7"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Running pre-meeting notification command {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d9d68876-323b-42bd-9953-635da8008f2e","trace_id":"6c00ce4c-f2df-4ccb-908d-4862de973096"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 09:55:00, 2026-04-14 10:00:00] {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: [conference:monitor:start] start ok {"activity_id":407307} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"27d73fea-a8cb-4757-9bbb-fbe7222e6943","trace_id":"bcdb88d0-a641-43e4-85e8-b7dc4ab20a0e"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:00","to":"10:05"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"23:55","to":"00:00"} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"588bae45-3998-4b86-b056-51aaf8b6882f","trace_id":"8f716a65-7768-44c5-b0e4-02a859f329e5"}
[2026-04-14 10:05:30] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:30] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:31] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"da835e46-68f6-4d4c-bf42-aa41de2e599d","trace_id":"7d96dad0-2325-4190-bff6-a2d83f01c4e1"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"73b127c9-c033-4fa7-a8ed-2e28077ba970","trace_id":"fd1cf3c5-d4ce-41d6-a19d-5e67fd544d9e"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-14T10:07:34.196472Z"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b2ca83a-1263-4a7c-a5d7-86dc15ea7aec","trace_id":"fbf48d3d-4e50-4442-b14f-d2ae5e6e70bd"}
[2026-04-14 10:05:34] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:39] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:44] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:05:59] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:00] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"44f01bd8-2599-41a4-b7c3-8bb1fdf0df9a","trace_id":"b4c87c8d-120d-48fb-b13c-aee84323f3e2"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8403c1e4-b5b6-41f9-9280-ff666beed28b","trace_id":"3f631f60-6d14-44f5-b6c3-b401999867f9"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring start {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:08] local.NOTICE: Monitoring end {"correlation_id":"0d51eb08-f2f1-4322-97d0-268700856a99","trace_id":"92b45b9a-b6e8-40c8-a783-193cf1a4eea2"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"0566c59b-bf67-49eb-970a-a6b955ddd756","trace_id":"724c5f5e-cd88-41fb-9b7e-74315e131024"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"33d190d4-0abb-4e63-af52-95faa913e7a1","trace_id":"703aaa4d-a551-4ed1-8a4a-609d373e2fc0"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:04:00, 2026-04-14 10:06:00] {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"da41d77d-46d9-41ae-90b7-f8418b05fb9a","trace_id":"47329a55-e6f4-406b-8730-e72d69737f09"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"310e7e99-6de4-4f93-967e-37c78e9ec826","trace_id":"2a1fdf0a-be0e-4e5a-a677-fa16ddc33e9e"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":1} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"de8430ad-f242-4536-bebe-e165320b6b26","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:22] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"eb46c86e-5c80-48b1-a4ee-ac425f197754","trace_id":"75f83d2f-4e5e-4086-b062-7c1d24b5b74c"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":56,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":169.9,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.75} {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:06:30] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"8dfaefe8-60b1-4846-bfa3-eb703c71deb8","trace_id":"186e2ff6-96dc-4ae6-a31b-0c3849678f7b"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"53329974-bdd6-4cd2-a022-0d565b0a5cf4","trace_id":"08246763-f961-45fb-b262-51378bdbc955"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2b6586f0-419e-4422-8800-e4b7619c43d6","trace_id":"221c682d-c45f-4e4a-a793-ed11006dc6a1"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring start {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:08] local.NOTICE: Monitoring end {"correlation_id":"9ba692ee-5286-41ca-abf0-86f6e1167a8e","trace_id":"d58ab8ad-7737-4d7c-91c0-81733c21e709"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"28e708ed-74ee-4417-a8da-c1a9ae0cd745","trace_id":"bd848201-9700-40bf-8fa4-9df13ae88f6e"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"95326c5a-7b17-475d-9245-634d15d51bfe","trace_id":"31b36f9d-93cc-4917-bc7b-6b6a3da139e7"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"f4f68165-0bcc-4e79-89e5-81f9c627e4dc","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:07:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"a7d5580f-8b90-4ff6-91e8-de94514f8ca1","trace_id":"fc40909d-86c1-4e45-bd51-96e2bd8c4556"}
[2026-04-14 10:07:16] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 0 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"df4d1442-bcd6-4b63-8513-802ae90993e6","trace_id":"5766aab2-0316-400f-aa89-168ea0579941"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"43299ccb-af86-42b1-b793-4ede57e2b91d","trace_id":"2f919b69-9d68-488c-89fa-2aade6ef8519"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"cde80757-333e-4268-a902-892c031b2373","trace_id":"36e932e0-8362-432f-9e36-d819f902d49e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring start {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:07] local.NOTICE: Monitoring end {"correlation_id":"146390f8-e91a-4e5c-b282-0c83b4947101","trace_id":"fb689e40-c867-4fc5-9847-c223835da80e"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"58e23da5-8f3f-4836-aac7-315ab32f74a3","trace_id":"c71267c4-b5fe-42ad-9bcd-76f6299c1538"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"9158bb6f-c165-495a-9b6e-8077d8343c87","trace_id":"df378af8-cacb-41ea-b030-b8f53144fddd"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:06:00, 2026-04-14 10:08:00] {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"35774df7-e046-49ec-835f-45f9dc1ec025","trace_id":"45c5e102-6b65-4d23-9edd-684ca996316c"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:08:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d4dc4b3b-8dc8-4665-aec7-139be0df2306","trace_id":"853f2210-e804-40aa-a1d8-4457c60f4993"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"8052fb2f-5876-4e2c-8f64-c770d9fe37fe","trace_id":"63249c19-bf59-4e52-9096-156d537a9786"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1dd274b4-f71d-486e-b6c4-aaf106304102","trace_id":"1d55139c-a58d-4456-801a-9bcb3bf9b024"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring start {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:10] local.NOTICE: Monitoring end {"correlation_id":"09dc762e-2b78-4dbb-a880-5c8f1cbfc4ad","trace_id":"8e60392d-4ea4-41f2-be2f-a01853c7a1ab"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"2de9f84e-2691-459d-adfd-f5f6746501d0","trace_id":"418ad958-86c0-4477-b0fb-e487768d6fa1"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"1be8488c-ae59-4f42-8475-c426905c3ca6","trace_id":"aeb8b412-635e-4929-915f-fb3fe5786eae"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"b3d1b688-309f-4df0-9dcd-9a733c87f597","trace_id":"f36f2d5a-efd0-474a-afd8-d4ec2ff4c60c"}
[2026-04-14 10:09:21] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"c7aba065-c8f1-473d-b8b5-4797245873bf","trace_id":"48f587f9-dd77-4634-9ad9-1137b029b5f5"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"7310aae5-3829-4d5c-bbc9-f6550a35f539","trace_id":"6099cf10-45e2-4b82-9649-649e2f8a1209"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5119d94e-0c61-4312-a740-2b569388cc9f","trace_id":"6a412a32-3bc3-46a4-af7c-24ad560a4e41"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring start {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:08] local.NOTICE: Monitoring end {"correlation_id":"95cdb3b7-b356-4325-be28-20663ece6aa0","trace_id":"e64a75a4-2493-4f35-ba46-ccd002e6b7b7"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"6460dd5f-4b94-4cd1-867e-53304d785744","trace_id":"683b103d-2dfc-4266-84a2-d5cd799b133f"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"5a59007d-2a12-4977-8a5a-59c678aa12ee","trace_id":"bc3a2ad5-9f13-4728-adaa-529da75cf011"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Running conference:monitor:count command for activities in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: [conference:monitor:count] No activities found in (2026-04-14 10:08:00, 2026-04-14 10:10:00] {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"afe2ff6b-1b9e-4485-bdad-a758d873a782","trace_id":"d507becf-55d6-4fda-8831-e981f95b85c8"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"ebe2ad2b-df9a-48d7-b78f-d52b7489c6f0","trace_id":"05b44216-d5ac-44cd-b74b-3e92567b192d"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d76b00ee-6271-4df7-ba45-769738bbb7ae","trace_id":"92384790-9588-452d-abea-cdd25a2fe7ff"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Running pre-meeting notification command {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"c8f3b5c1-bef5-4584-b9d8-0c10578fefac","trace_id":"b700f03d-b02b-495e-b505-1598c7a9ea4b"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Running conference:monitor:start command for activities in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: [conference:monitor:start] No activities found in (2026-04-14 10:00:00, 2026-04-14 10:05:00] {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"d7b97fbb-00b9-47c2-ae81-dda03fd8b253","trace_id":"54447306-49bf-4629-8de0-bb191c61cce2"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"10:05","to":"10:10"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"00:00","to":"00:05"} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.746,"memoryPeakAfterCommandInMB":99.746} {"correlation_id":"07665538-664f-421e-af9b-97152a688b68","trace_id":"d116813e-2d96-4a3d-ae04-6e53d69ca565"}
[2026-04-14 10:10:25] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda111b53"}
[2026-04-14 10:10:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"d269668d-cb18-4aeb-9a05-13a9e53195ed","trace_id":"ecbe2fde-809f-4720-8113-609eda11...
|
NULL
|
|
74866
|
1863
|
34
|
2026-04-23T10:21:47.818222+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776939707818_m1.jpg...
|
Sequel Ace
|
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_c (MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"TABLES","depth":6,"value":"TABLES","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_categories","depth":6,"value":"playbook_categories","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_layouts","depth":6,"value":"playbook_layouts","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbooks","depth":6,"value":"playbooks","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook","depth":3,"automation_id":"_NS:879","value":"playbook","placeholder":"Filter","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"search","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"cancel","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":2,"automation_id":"_NS:4671","help_text":"Add new table","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Quick Look","depth":2,"automation_id":"_NS:1133","help_text":"Toggle the visibility of the Information panel","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":2,"automation_id":"_NS:1882","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"refresh","depth":2,"automation_id":"_NS:3295","help_text":"Refresh table list","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"id","depth":7,"value":"id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"PRI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"auto_increment","depth":7,"value":"auto_increment","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"uuid","depth":7,"value":"uuid","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"BINARY","depth":7,"value":"BINARY","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"16","depth":7,"value":"16","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"UNI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"playbook_id","depth":7,"value":"playbook_id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"MUL","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"type","depth":7,"value":"type","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"CHAR","depth":7,"value":"CHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"20","depth":7,"value":"20","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"all","depth":7,"value":"all","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"name","depth":7,"value":"name","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"VARCHAR","depth":7,"value":"VARCHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"150","depth":7,"value":"150","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"ai_prompt_description","depth":7,"value":"ai_prompt_description","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TEXT","depth":7,"value":"TEXT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":false,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_selectable","depth":7,"value":"is_selectable","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"sequence","depth":7,"value":"sequence","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"3","depth":7,"value":"3","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"was_answered","depth":7,"value":"was_answered","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_default","depth":7,"value":"is_default","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"created_at","depth":7,"value":"created_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false}]...
|
8056798269019544680
|
361465088795263031
|
app_switch
|
accessibility
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP...
|
NULL
|
|
74896
|
1865
|
8
|
2026-04-23T10:23:49.557507+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776939829557_m1.jpg...
|
Sequel Ace
|
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_c (MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"TABLES","depth":6,"value":"TABLES","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_categories","depth":6,"value":"playbook_categories","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_layouts","depth":6,"value":"playbook_layouts","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbooks","depth":6,"value":"playbooks","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook","depth":3,"automation_id":"_NS:879","value":"playbook","placeholder":"Filter","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"search","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"cancel","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":2,"automation_id":"_NS:4671","help_text":"Add new table","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Quick Look","depth":2,"automation_id":"_NS:1133","help_text":"Toggle the visibility of the Information panel","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":2,"automation_id":"_NS:1882","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"refresh","depth":2,"automation_id":"_NS:3295","help_text":"Refresh table list","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"id","depth":7,"value":"id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"PRI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"auto_increment","depth":7,"value":"auto_increment","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"uuid","depth":7,"value":"uuid","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"BINARY","depth":7,"value":"BINARY","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"16","depth":7,"value":"16","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"UNI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"playbook_id","depth":7,"value":"playbook_id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"MUL","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"type","depth":7,"value":"type","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"CHAR","depth":7,"value":"CHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"20","depth":7,"value":"20","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"all","depth":7,"value":"all","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"name","depth":7,"value":"name","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"VARCHAR","depth":7,"value":"VARCHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"150","depth":7,"value":"150","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"ai_prompt_description","depth":7,"value":"ai_prompt_description","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TEXT","depth":7,"value":"TEXT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":false,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_selectable","depth":7,"value":"is_selectable","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"sequence","depth":7,"value":"sequence","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"3","depth":7,"value":"3","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"was_answered","depth":7,"value":"was_answered","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_default","depth":7,"value":"is_default","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"created_at","depth":7,"value":"created_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"updated_at","depth":7,"value":"updated_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXButton","text":"Field","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Type","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Length","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Unsigned","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Zerofill","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Binary","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Allow Null","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Default","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Extra","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Encoding","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"smart item","depth":4,"automation_id":"_NS:3756","help_text":"Edit Table Details (⌘4)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":4,"automation_id":"_NS:1160","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:4152","help_text":"Delete selected field (⌫)","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2110","help_text":"Refresh table structure (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:1610","help_text":"Add field (⌥⌘A)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"PRIMARY","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_uuid_unique","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"uuid","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_playbook_id_index","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1254","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXButton","text":"Non_unique","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false}]...
|
65816182224957502
|
865986925664190642
|
app_switch
|
accessibility
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name...
|
NULL
|
|
74832
|
1863
|
20
|
2026-04-23T10:19:22.494940+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776939562494_m1.jpg...
|
Sequel Ace
|
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_c (MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name
Seq_in_index
Column_name
Collation
Cardinality
Sub_part
Packed
Comment
remove
refresh
add
INDEXES
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"TABLES","depth":6,"value":"TABLES","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_categories","depth":6,"value":"playbook_categories","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_layouts","depth":6,"value":"playbook_layouts","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbooks","depth":6,"value":"playbooks","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook","depth":3,"automation_id":"_NS:879","value":"playbook","placeholder":"Filter","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"search","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"cancel","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":2,"automation_id":"_NS:4671","help_text":"Add new table","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Quick Look","depth":2,"automation_id":"_NS:1133","help_text":"Toggle the visibility of the Information panel","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":2,"automation_id":"_NS:1882","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"refresh","depth":2,"automation_id":"_NS:3295","help_text":"Refresh table list","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"id","depth":7,"value":"id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"PRI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"auto_increment","depth":7,"value":"auto_increment","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"uuid","depth":7,"value":"uuid","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"BINARY","depth":7,"value":"BINARY","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"16","depth":7,"value":"16","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"UNI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"playbook_id","depth":7,"value":"playbook_id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"MUL","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"type","depth":7,"value":"type","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"CHAR","depth":7,"value":"CHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"20","depth":7,"value":"20","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"all","depth":7,"value":"all","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"name","depth":7,"value":"name","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"VARCHAR","depth":7,"value":"VARCHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"150","depth":7,"value":"150","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"ai_prompt_description","depth":7,"value":"ai_prompt_description","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TEXT","depth":7,"value":"TEXT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":false,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_selectable","depth":7,"value":"is_selectable","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"sequence","depth":7,"value":"sequence","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"3","depth":7,"value":"3","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"was_answered","depth":7,"value":"was_answered","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_default","depth":7,"value":"is_default","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"created_at","depth":7,"value":"created_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"updated_at","depth":7,"value":"updated_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXButton","text":"Field","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Type","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Length","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Unsigned","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Zerofill","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Binary","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Allow Null","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Default","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Extra","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Encoding","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"smart item","depth":4,"automation_id":"_NS:3756","help_text":"Edit Table Details (⌘4)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":4,"automation_id":"_NS:1160","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:4152","help_text":"Delete selected field (⌫)","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2110","help_text":"Refresh table structure (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:1610","help_text":"Add field (⌥⌘A)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"PRIMARY","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_uuid_unique","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"uuid","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_playbook_id_index","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1254","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXButton","text":"Non_unique","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Seq_in_index","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Column_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Cardinality","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Sub_part","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Packed","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:1930","help_text":"Delete selected index","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2593","help_text":"Refresh table indexes (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:3332","help_text":"Add index","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"INDEXES","depth":4,"automation_id":"_NS:3948","role_description":"text"},{"role":"AXStaticText","text":"(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories","depth":1,"role_description":"text"}]...
|
-3855442780064241766
|
884529157329404594
|
app_switch
|
accessibility
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name
Seq_in_index
Column_name
Collation
Cardinality
Sub_part
Packed
Comment
remove
refresh
add
INDEXES
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
NULL
|
|
74868
|
1863
|
35
|
2026-04-23T10:21:49.568765+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776939709568_m1.jpg...
|
Sequel Ace
|
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_c (MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name
Seq_in_index
Column_name
Collation
Cardinality
Sub_part
Packed
Comment
remove
refresh
add
INDEXES
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"TABLES","depth":6,"value":"TABLES","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_categories","depth":6,"value":"playbook_categories","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_layouts","depth":6,"value":"playbook_layouts","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbooks","depth":6,"value":"playbooks","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook","depth":3,"automation_id":"_NS:879","value":"playbook","placeholder":"Filter","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"search","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"cancel","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":2,"automation_id":"_NS:4671","help_text":"Add new table","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Quick Look","depth":2,"automation_id":"_NS:1133","help_text":"Toggle the visibility of the Information panel","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":2,"automation_id":"_NS:1882","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"refresh","depth":2,"automation_id":"_NS:3295","help_text":"Refresh table list","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"id","depth":7,"value":"id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"PRI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"auto_increment","depth":7,"value":"auto_increment","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"uuid","depth":7,"value":"uuid","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"BINARY","depth":7,"value":"BINARY","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"16","depth":7,"value":"16","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"UNI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"playbook_id","depth":7,"value":"playbook_id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"MUL","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"type","depth":7,"value":"type","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"CHAR","depth":7,"value":"CHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"20","depth":7,"value":"20","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"all","depth":7,"value":"all","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"name","depth":7,"value":"name","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"VARCHAR","depth":7,"value":"VARCHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"150","depth":7,"value":"150","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"ai_prompt_description","depth":7,"value":"ai_prompt_description","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TEXT","depth":7,"value":"TEXT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":false,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_selectable","depth":7,"value":"is_selectable","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"sequence","depth":7,"value":"sequence","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"3","depth":7,"value":"3","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"was_answered","depth":7,"value":"was_answered","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_default","depth":7,"value":"is_default","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"created_at","depth":7,"value":"created_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"updated_at","depth":7,"value":"updated_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXButton","text":"Field","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Type","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Length","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Unsigned","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Zerofill","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Binary","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Allow Null","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Default","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Extra","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Encoding","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"smart item","depth":4,"automation_id":"_NS:3756","help_text":"Edit Table Details (⌘4)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":4,"automation_id":"_NS:1160","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:4152","help_text":"Delete selected field (⌫)","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2110","help_text":"Refresh table structure (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:1610","help_text":"Add field (⌥⌘A)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"PRIMARY","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_uuid_unique","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"uuid","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_playbook_id_index","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1254","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXButton","text":"Non_unique","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Seq_in_index","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Column_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Cardinality","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Sub_part","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Packed","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:1930","help_text":"Delete selected index","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2593","help_text":"Refresh table indexes (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:3332","help_text":"Add index","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"INDEXES","depth":4,"automation_id":"_NS:3948","role_description":"text"},{"role":"AXStaticText","text":"(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories","depth":1,"role_description":"text"}]...
|
-3855442780064241766
|
884529157329404594
|
visual_change
|
accessibility
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name
Seq_in_index
Column_name
Collation
Cardinality
Sub_part
Packed
Comment
remove
refresh
add
INDEXES
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
74866
|
|
74898
|
1865
|
9
|
2026-04-23T10:23:50.536199+00:00
|
/Users/lukas/.screenpipe/data/data/2026-04-23/1776 /Users/lukas/.screenpipe/data/data/2026-04-23/1776939830536_m1.jpg...
|
Sequel Ace
|
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_c (MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name
Seq_in_index
Column_name
Collation
Cardinality
Sub_part
Packed
Comment
remove
refresh
add
INDEXES
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"TABLES","depth":6,"value":"TABLES","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_categories","depth":6,"value":"playbook_categories","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook_layouts","depth":6,"value":"playbook_layouts","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbooks","depth":6,"value":"playbooks","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"playbook","depth":3,"automation_id":"_NS:879","value":"playbook","placeholder":"Filter","role_description":"search text field","subrole":"AXSearchField","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"search","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"cancel","depth":4,"role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":2,"automation_id":"_NS:4671","help_text":"Add new table","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Quick Look","depth":2,"automation_id":"_NS:1133","help_text":"Toggle the visibility of the Information panel","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":2,"automation_id":"_NS:1882","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"refresh","depth":2,"automation_id":"_NS:3295","help_text":"Refresh table list","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"id","depth":7,"value":"id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"PRI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"auto_increment","depth":7,"value":"auto_increment","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"uuid","depth":7,"value":"uuid","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"BINARY","depth":7,"value":"BINARY","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"16","depth":7,"value":"16","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"UNI","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"playbook_id","depth":7,"value":"playbook_id","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"INT","depth":7,"value":"INT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"10","depth":7,"value":"10","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"MUL","depth":7,"role_description":"text"},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"type","depth":7,"value":"type","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"CHAR","depth":7,"value":"CHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"20","depth":7,"value":"20","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"all","depth":7,"value":"all","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"name","depth":7,"value":"name","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"VARCHAR","depth":7,"value":"VARCHAR","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"150","depth":7,"value":"150","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"ai_prompt_description","depth":7,"value":"ai_prompt_description","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TEXT","depth":7,"value":"TEXT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":false,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_selectable","depth":7,"value":"is_selectable","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"sequence","depth":7,"value":"sequence","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"3","depth":7,"value":"3","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"was_answered","depth":7,"value":"was_answered","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"is_default","depth":7,"value":"is_default","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TINYINT","depth":7,"value":"TINYINT","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"1","depth":7,"value":"1","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXTextField","text":"0","depth":7,"value":"0","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"created_at","depth":7,"value":"created_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"updated_at","depth":7,"value":"updated_at","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"TIMESTAMP","depth":7,"value":"TIMESTAMP","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXTextField","text":"NULL","depth":7,"value":"NULL","role_description":"text field","is_enabled":true,"is_focused":false},{"role":"AXComboBox","text":"None","depth":7,"value":"None","role_description":"combo box","is_enabled":true,"is_focused":false,"is_expanded":false},{"role":"AXButton","text":"Field","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Type","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Length","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Unsigned","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Zerofill","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Binary","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Allow Null","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Default","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Extra","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Encoding","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"smart item","depth":4,"automation_id":"_NS:3756","help_text":"Edit Table Details (⌘4)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXMenuButton","text":"action","depth":4,"automation_id":"_NS:1160","role_description":"menu button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:4152","help_text":"Delete selected field (⌫)","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2110","help_text":"Refresh table structure (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:1610","help_text":"Add field (⌥⌘A)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"PRIMARY","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"0","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_uuid_unique","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"uuid","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"25080","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_categories_playbook_id_index","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"playbook_id","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"A","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"1254","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXStaticText","text":"NULL","depth":7,"role_description":"text"},{"role":"AXButton","text":"Non_unique","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Key_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Seq_in_index","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Column_name","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Collation","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Cardinality","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Sub_part","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Packed","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"Comment","depth":7,"role_description":"sort button","subrole":"AXSortButton","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"remove","depth":4,"automation_id":"_NS:1930","help_text":"Delete selected index","role_description":"button","is_enabled":false,"is_focused":false},{"role":"AXButton","text":"refresh","depth":4,"automation_id":"_NS:2593","help_text":"Refresh table indexes (⌘R)","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXButton","text":"add","depth":4,"automation_id":"_NS:3332","help_text":"Add index","role_description":"button","is_enabled":true,"is_focused":false},{"role":"AXStaticText","text":"INDEXES","depth":4,"automation_id":"_NS:3948","role_description":"text"},{"role":"AXStaticText","text":"(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories","depth":1,"role_description":"text"}]...
|
-3855442780064241766
|
884529157329404594
|
visual_change
|
accessibility
|
NULL
|
TABLES
playbook_categories
playbook_layouts
playbo TABLES
playbook_categories
playbook_layouts
playbooks
playbook
search
cancel
add
Quick Look
action
refresh
id
INT
10
PRI
auto_increment
uuid
BINARY
16
UNI
None
playbook_id
INT
10
MUL
None
type
CHAR
20
all
None
name
VARCHAR
150
None
ai_prompt_description
TEXT
NULL
None
is_selectable
TINYINT
1
1
None
sequence
TINYINT
3
0
None
was_answered
TINYINT
1
1
None
is_default
TINYINT
1
0
None
created_at
TIMESTAMP
NULL
None
updated_at
TIMESTAMP
NULL
None
Field
Type
Length
Unsigned
Zerofill
Binary
Allow Null
Key
Default
Extra
Encoding
Collation
Comment
smart item
action
remove
refresh
add
0
PRIMARY
1
id
A
25080
NULL
NULL
0
playbook_categories_uuid_unique
1
uuid
A
25080
NULL
NULL
1
playbook_categories_playbook_id_index
1
playbook_id
A
1254
NULL
NULL
Non_unique
Key_name
Seq_in_index
Column_name
Collation
Cardinality
Sub_part
Packed
Comment
remove
refresh
add
INDEXES
(MySQL 11.4.9-MariaDB-log) PROD/jiminny/playbook_categories...
|
74896
|