|
70917
|
2547
|
3
|
2026-05-22T10:50:40.470650+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779447040470_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.6911569,"top":0.10055866,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.36269948,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70907
|
NULL
|
NULL
|
NULL
|
|
70916
|
2546
|
3
|
2026-05-22T10:50:39.320802+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779447039320_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70912
|
NULL
|
NULL
|
NULL
|
|
70915
|
2547
|
2
|
2026-05-22T10:50:10.006617+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779447010006_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.6911569,"top":0.10055866,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.36269948,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70907
|
NULL
|
NULL
|
NULL
|
|
70914
|
2546
|
2
|
2026-05-22T10:50:08.934908+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779447008934_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70912
|
NULL
|
NULL
|
NULL
|
|
70913
|
2547
|
1
|
2026-05-22T10:49:39.581566+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446979581_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.6911569,"top":0.10055866,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.36269948,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70907
|
NULL
|
NULL
|
NULL
|
|
70912
|
2546
|
1
|
2026-05-22T10:49:38.491581+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446978491_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70911
|
2547
|
0
|
2026-05-22T10:49:08.971678+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446948971_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.6911569,"top":0.10055866,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.36269948,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70907
|
NULL
|
NULL
|
NULL
|
|
70910
|
2546
|
0
|
2026-05-22T10:49:06.981263+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446946981_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4047408223668449518
|
-8636777727364969024
|
idle
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)О 882APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete mode100644 app/Component/ES/Processor/Traits/WorkerIdTrait.phpcreate mode 100644 app/Component/ES/QueuePriorityEnum.phpTrginusWorker/aTrai+: Phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "o Nikolay YankovP. Aneliya Angelova. Stoyan Tanevdo James Graham> 0100% С8 • Fri 22 May 13:49:06Describe what you are looking for# releases8 22•MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
70908
|
NULL
|
NULL
|
NULL
|
|
70909
|
NULL
|
0
|
2026-05-22T10:48:38.562835+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446918562_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.6911569,"top":0.10055866,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.36269948,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70907
|
NULL
|
NULL
|
NULL
|
|
70908
|
NULL
|
0
|
2026-05-22T10:48:36.637452+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446916637_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70907
|
2545
|
49
|
2026-05-22T10:48:07.926088+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446887926_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"bounds":{"left":0.6911569,"top":0.10055866,"width":0.009973404,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.36269948,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70906
|
2544
|
40
|
2026-05-22T10:48:06.190711+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446886190_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"22","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Activity\\CrmOwnerResolver;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n try {\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $team = $user->getTeam();\n\n try {\n $crmResolver = app(CrmOwnerResolver::class, [\n 'team' => $team,\n 'integrationAdmin' => $user,\n 'providerSlug' => $team->getCrmConfiguration()->getProviderName(),\n ]);\n $crmService = $crmResolver->prepareCrmService();\n } catch (\\Throwable $throwable) {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n }\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5111928977143741995
|
-3204935344002590708
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
22
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
try {
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$team = $user->getTeam();
try {
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $user,
'providerSlug' => $team->getCrmConfiguration()->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
} catch (\Throwable $throwable) {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
}
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70904
|
NULL
|
NULL
|
NULL
|
|
70905
|
2545
|
48
|
2026-05-22T10:47:34.616941+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446854616_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5187215517464698470
|
-8348545152461722172
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
PhostormFV faVsco.jsroledey© InsightsService.php© NumberAllocator.phpc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilot_ WebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.phpC)InsiahtSeatService.oho© InstantMeetingService.php© IntercomService.php© IpapiClient.php(c) Inan Service nhn© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorinterface.phpkesolveleamermeonnection.ong© SimpleThrottleService.php@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.php580>123 19)c Userservice.onrLocal changesShelfConsole xLogx +venv.local aor@.liminnvDehuaCommandinhn annlConcole/CommanddColalalallale walalewalat© SoftPhoneManager.php app/Component/Twilio/Conference/ConferenceManac© TextMessagingService.php app/Services/TelephonyUnversioned Files 9 filesE.env.nikilocal appE.env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R6 favicon.ico publicEids.txt aprTraw saL querv.sal appC) SimulateWebhooksCommand.oho app/Console/Commands/Crm/HubspotM+ WEBHOOK FILTERING IMPLEMENTATION.md appPatch succeccfullv annlied (maments aaolConvertLeadActivities.phpsynerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4KLAY7а (61pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeporcsservice saucomareakeportsserviceprivate readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vilautomated-reports/interest 1 usagepublic function trackInterest(Request Srequest): JsonResponsef...?* othrows AoplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuidhpublic function delete(Request Srequest, string $uuid): JsonResponsel...}=custom.log= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php X A console [EU]A console [STAGING]stohenamespace Jininny services lelephony>use ...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient Stwilion ient = nulupublic function __construct(private readonly ProviderRegistry SproviderRegistry.private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,* Othrows ConfigurationExceptionpublic function setTeam(Team $team): voidsth1s->team = steam:Sthis-›twilioClient = Sthis->twilioClientBuilder->build(Steam):* Athrows \Exceptionoublic function sendUser Sfrom. strina Sto. strina Smessage): MessagelnstancelA2 . V100% 2. Fri 22 May 13:47:34AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0 ..The file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrnckcaloneFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownGal .Ask anything (&OL)+ → @ Side-by-side viewer ~Do not ianore v81aee7aad config/logging.php"driver' => 'errorlog'..'Level' => env('LOG_LEVEL', 'info'),"nath' => stonade nath('oas/anavel.iog')l1.XBB|?1 differenco"level' => envC'LOG LEVEL', Cinfo").'path' => storage_path('logs/Laravel.log'),"custom channelt =>fdnivent => Isinale!!'path' => storage_path('logs/custom.log'),'level' => env('LOG_LEVEL', 'info').W Windsurf TeamcPo 4 spaces...
|
70903
|
NULL
|
NULL
|
NULL
|
|
70904
|
2544
|
39
|
2026-05-22T10:47:32.165466+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446852165_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Patch successfully applied
text/html
text/html
tex Patch successfully applied
text/html
text/html
text/html
Project: faVsco.js, menu...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Patch successfully applied","depth":3,"on_screen":true,"value":"Patch successfully applied","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,"on_screen":false,"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,"on_screen":true,"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,"on_screen":false,"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,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4698258168744803567
|
3176677808885292200
|
click
|
hybrid
|
NULL
|
Patch successfully applied
text/html
text/html
tex Patch successfully applied
text/html
text/html
text/html
Project: faVsco.js, menu
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKER₴81DEV (-zsh)O $82APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete mode100644 app/Component/ES/Processor/Traits/WorkerIdTrait.phpcreate mode 100644 app/Component/ES/QueuePriorityEnum.phpTrginusWorker/aTrai+: Phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "R. Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:47:31Describe what you are looking for# releases8 22•MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70903
|
2545
|
47
|
2026-05-22T10:47:31.301237+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446851301_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Patch successfully applied
text/html
text/html
tex Patch successfully applied
text/html
text/html
text/html
Project: faVsco.js, menu
master, menu...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Patch successfully applied","depth":3,"bounds":{"left":0.016954787,"top":0.858739,"width":0.05418883,"height":0.013567438},"on_screen":true,"value":"Patch successfully applied","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,"on_screen":false,"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.858739,"width":0.05418883,"height":0.013567438},"on_screen":true,"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,"on_screen":false,"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},"on_screen":true,"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},"on_screen":true,"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}]...
|
2602427871906152787
|
-6046711822437196672
|
visual_change
|
hybrid
|
NULL
|
Patch successfully applied
text/html
text/html
tex Patch successfully applied
text/html
text/html
text/html
Project: faVsco.js, menu
master, menu
PhostormINavicarecodeKeractorFV faVsco.js°9 master kroledey© InsightsService.php© NumberAllocator.php1extmessagingservice.ongc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilot0 WebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.php@ InsiahtSeatService.ohn© InstantMeetingService.php© IntercomService.php© IpapiClient.php© IpapiService.php© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorInterface.php© ResolveTeamCrmConnection.php© SimpleThrottleService.php@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.phpc Userservice.onrConvertLeadActivities.phpsynerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C) AskAnythingPromptService.pho© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerA14 K2 A V70 (61pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeporcsservice saucomareakeportsservice.private readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vilautomated-reports/interest 1 usagepublic function trackInterest(Request Srequest): JsonResponsef...,* othrows ApplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuid123 19)public function delete(Request Srequest, string $uuid): JsonResponsel...}Local changesConsole xLog XО. л> sunc vavoook caregores ana crm tieia vatues lu tues. yesteroay 14?u4> Scim provisionina with rioles 8 files. 19.05.26. 11:06]→alDo not ignoreHighlight words 11 l ?• base version app/componvuse Jiminny Excentions LogicExcention:vCrmownerResolverl SoftPhoneManager.php app/Component/Twilio/Conference/ConferenceManag-PнE TextMessagingService.php app/Services/TelephonyJY-20991 SMS Teyt relav FU 2 files 15.05.26. 14:05use Jiminny Models.use Jiminny Models Particinant Connection:use Jiminny Services ActivityService;use Jiminny Services Crm. cachedcrmServicelecaraton.ID 8 SendRenort.loh 21Mach rdeccenmuy gpol8s 26 10.00los 13.05.26 12-381use Jiminny (Services\Crm \ProviderRegistry:use liminnv Services Telenhonv TwilioClientRuilden•Move non nadinator soarch calls to client-scoarch 1 file 11.05 26 10:07_use Psr\Log\LoggerInterface;) Almact roady ono call ta ho toctad 9 filac 9 05 26 11-51hubenot rato limit tomn chockout 17 filac 9 05 26 11:19Scontact = null-cavod coarch dolata 1 filo 27 04 26 11.59Cetado - null•› expiration message 6 files, 27.04.26, 9:23Connontunitv = null.= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]© TextMessagingService.php X# console [euyA console [STAGING]namespace Jininny services lelephonyuse ...Activ1ty CrmuwnerResolver:use Jiminny\Services\ Crm\CachedCrmServiceDecorator:use Jiminny Services Crm ProviderReq1stry:use Tw1l10 Exceptions Contiqurat1onexcept1on:use Tw1l10 Exceptions TW1L10Except1on:use w1L1o Rest Ao1 V2010 Account Messacejinstance:class TextMessagingServiceprivate ?Team $team = null;private ?TwilioClient $twilioClient = null;public function __construct(private readonly ProviderRegistry $providerRegistry,private readonly ActivityService SactivityServiceprivate readonly Webhook SurlGenenatonprivate readonly TwilioClientBuilder StwilioClientBuilder,x acnows conticuraczoncxcepcionpublic tunction setleam leam steam: vo1dsth1s->team = steam:Sthis->twilioClient = Sthis->twilioClientBuilder->build(Steam):/**100% 52. Fri 22 May 13:47:30U AskJiminnyReportActivityServiceTest v& console [PROD]CascadeInvestigate Foreign KeSalestorce Token Fallb+0..116 v1 V1 A VThe file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnckcaloneThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added therinal Implementation summary:1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownGal .Ask anything (&OL)2 differencas8 Shelved versionuse Jiminny Excentions LogicExcention:use Jiminny\Models;use Jiminnv\Models\ Particinant\ Connection:use Jiminny Services\ActivityService;use Jiminnv Services Activitv CrmiwnerResol ver.luse .liminnv|Services|Con\CachedCemServiceleconaton•luse liminnv Services Crn. ProviderRegistny.luse liminnv|Services| Telenhonv| TwilioflientRuilden,luse Pen loa. I nagerIntenface•Ccontant = null+$stage = null;W Windsurf TeamcPo 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70902
|
2544
|
38
|
2026-05-22T10:47:29.649529+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446849649_m1.jpg...
|
PhpStorm
|
Unshelve Changes
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Patch file name:
/Users/lukas/jiminny/app/.idea/sh Patch file name:
/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch
Browse… (⇧⏎)
Show Difference...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Patch file name:","depth":1,"on_screen":false,"role_description":"text"},{"role":"AXTextField","text":"/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch","depth":1,"on_screen":false,"value":"/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse… (⇧⏎)","depth":2,"bounds":{"left":0.0,"top":0.0,"width":0.013888889,"height":0.022222223},"on_screen":false,"help_text":"Browse… (⇧⏎)","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Difference","depth":2,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-683124680939992861
|
-7737183942842253213
|
click
|
accessibility
|
NULL
|
Patch file name:
/Users/lukas/jiminny/app/.idea/sh Patch file name:
/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch
Browse… (⇧⏎)
Show Difference...
|
70898
|
NULL
|
NULL
|
NULL
|
|
70901
|
2545
|
46
|
2026-05-22T10:47:29.536572+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446849536_m2.jpg...
|
PhpStorm
|
Unshelve Changes
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Patch file name:
/Users/lukas/jiminny/app/.idea/sh Patch file name:
/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch
Browse… (⇧⏎)
Show Difference
Change Directory Paths...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Patch file name:","depth":1,"on_screen":false,"role_description":"text"},{"role":"AXTextField","text":"/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch","depth":1,"on_screen":false,"value":"/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse… (⇧⏎)","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0066489363,"height":0.0},"on_screen":false,"help_text":"Browse… (⇧⏎)","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Difference","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Change Directory Paths","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
1588012255828197035
|
-3405210727581421199
|
click
|
accessibility
|
NULL
|
Patch file name:
/Users/lukas/jiminny/app/.idea/sh Patch file name:
/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch
Browse… (⇧⏎)
Show Difference
Change Directory Paths...
|
70900
|
NULL
|
NULL
|
NULL
|
|
70900
|
2545
|
45
|
2026-05-22T10:47:28.271173+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446848271_m2.jpg...
|
PhpStorm
|
Unshelve Changes
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Patch file name:
/Users/lukas/jiminny/app/.idea/sh Patch file name:
/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch
Browse… (⇧⏎)
Show Difference
Change Directory Paths
Group By
Expand All
Collapse All
app 2 files, folder checked
Component/Twilio/Conference/ConferenceManager 1 file, folder checked
SoftPhoneManager.php Modified, final class checked
Services/Telephony 1 file, folder checked
TextMessagingService.php Modified, class checked
Component/Twilio/Conference/ConferenceManager 1 file, folder checked
SoftPhoneManager.php Modified, final class checked
SoftPhoneManager.php Modified, final class checked
Services/Telephony 1 file, folder checked
TextMessagingService.php Modified, class checked
TextMessagingService.php Modified, class checked
Name:
Name:
Changes
Changes
Comment:
JY-20963 fix deleted object import
Set active
Track context
Help
Remove successfully applied files from the shelf
Cancel
Unshelve Changes
Unshelve Changes...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"Patch file name:","depth":1,"on_screen":false,"role_description":"text"},{"role":"AXTextField","text":"/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch","depth":1,"on_screen":false,"value":"/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Browse… (⇧⏎)","depth":2,"bounds":{"left":0.27027926,"top":1.0,"width":0.0066489363,"height":0.0},"on_screen":false,"help_text":"Browse… (⇧⏎)","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Show Difference","depth":2,"bounds":{"left":0.40791222,"top":0.31843576,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Change Directory Paths","depth":2,"bounds":{"left":0.41655585,"top":0.31843576,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Group By","depth":2,"bounds":{"left":0.4275266,"top":0.31843576,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand All","depth":2,"bounds":{"left":0.796875,"top":0.31843576,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":2,"bounds":{"left":0.8055186,"top":0.31843576,"width":0.008643617,"height":0.01915403},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app 2 files, folder checked","depth":4,"bounds":{"left":0.41589096,"top":0.34317636,"width":0.037898935,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Component/Twilio/Conference/ConferenceManager 1 file, folder checked","depth":5,"bounds":{"left":0.42220744,"top":0.36073422,"width":0.13364361,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SoftPhoneManager.php Modified, final class checked","depth":6,"bounds":{"left":0.42852393,"top":0.3782921,"width":0.08610372,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Services/Telephony 1 file, folder checked","depth":5,"bounds":{"left":0.42220744,"top":0.39584997,"width":0.06815159,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"TextMessagingService.php Modified, class checked","depth":6,"bounds":{"left":0.42852393,"top":0.41340783,"width":0.093417555,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Component/Twilio/Conference/ConferenceManager 1 file, folder checked","depth":4,"bounds":{"left":0.42220744,"top":0.36073422,"width":0.13364361,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SoftPhoneManager.php Modified, final class checked","depth":5,"bounds":{"left":0.42852393,"top":0.3782921,"width":0.08610372,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"SoftPhoneManager.php Modified, final class checked","depth":4,"bounds":{"left":0.42852393,"top":0.3782921,"width":0.08610372,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Services/Telephony 1 file, folder checked","depth":4,"bounds":{"left":0.42220744,"top":0.39584997,"width":0.06815159,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"TextMessagingService.php Modified, class checked","depth":5,"bounds":{"left":0.42852393,"top":0.41340783,"width":0.093417555,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"TextMessagingService.php Modified, class checked","depth":4,"bounds":{"left":0.42852393,"top":0.41340783,"width":0.093417555,"height":0.017557861},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"Name:","depth":1,"bounds":{"left":0.40757978,"top":0.5961692,"width":0.013297873,"height":0.013567438},"on_screen":true,"role_description":"text"},{"role":"AXComboBox","text":"Name:","depth":1,"bounds":{"left":0.42952126,"top":0.5897845,"width":0.3863032,"height":0.027134877},"on_screen":true,"role_description":"combo box","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Changes","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXTextArea","text":"Changes","depth":3,"bounds":{"left":0.43450797,"top":0.5961692,"width":0.36801863,"height":0.013567438},"on_screen":true,"value":"Changes","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Comment:","depth":1,"bounds":{"left":0.40757978,"top":0.63048685,"width":0.020944148,"height":0.013567438},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"JY-20963 fix deleted object import","depth":2,"bounds":{"left":0.43085107,"top":0.6241022,"width":0.38364363,"height":0.049481247},"on_screen":true,"value":"JY-20963 fix deleted object import","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Set active","depth":1,"bounds":{"left":0.40625,"top":0.67996806,"width":0.029587766,"height":0.019952115},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Track context","depth":1,"bounds":{"left":0.4398271,"top":0.67996806,"width":0.03756649,"height":0.019952115},"on_screen":true,"help_text":"Reload context (e.g. open editors) when changelist is set active","role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Help","depth":1,"bounds":{"left":0.40591756,"top":0.7086991,"width":0.00930851,"height":0.027134877},"on_screen":true,"help_text":"Show help contents","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Remove successfully applied files from the shelf","depth":1,"bounds":{"left":0.41522607,"top":0.7086991,"width":0.1143617,"height":0.027134877},"on_screen":true,"role_description":"checkbox","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Cancel","depth":1,"bounds":{"left":0.7386968,"top":0.7086991,"width":0.025930852,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Unshelve Changes","depth":1,"bounds":{"left":0.76662236,"top":0.7086991,"width":0.049534574,"height":0.027134877},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Unshelve Changes","depth":1,"bounds":{"left":0.59042555,"top":0.2897047,"width":0.041223403,"height":0.012769354},"on_screen":true,"role_description":"text"}]...
|
-2289464288901565827
|
-278648034763181993
|
visual_change
|
accessibility
|
NULL
|
Patch file name:
/Users/lukas/jiminny/app/.idea/sh Patch file name:
/Users/lukas/jiminny/app/.idea/shelf/CrmOwnerResolver/shelved.patch
Browse… (⇧⏎)
Show Difference
Change Directory Paths
Group By
Expand All
Collapse All
app 2 files, folder checked
Component/Twilio/Conference/ConferenceManager 1 file, folder checked
SoftPhoneManager.php Modified, final class checked
Services/Telephony 1 file, folder checked
TextMessagingService.php Modified, class checked
Component/Twilio/Conference/ConferenceManager 1 file, folder checked
SoftPhoneManager.php Modified, final class checked
SoftPhoneManager.php Modified, final class checked
Services/Telephony 1 file, folder checked
TextMessagingService.php Modified, class checked
TextMessagingService.php Modified, class checked
Name:
Name:
Changes
Changes
Comment:
JY-20963 fix deleted object import
Set active
Track context
Help
Remove successfully applied files from the shelf
Cancel
Unshelve Changes
Unshelve Changes...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70898
|
2544
|
37
|
2026-05-22T10:47:24.581039+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446844581_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-3088599111841370244
|
-8636775528843997756
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)О 882APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete mode100644 app/Component/ES/Processor/Traits/WorkerIdTrait.phpcreate mode 100644 app/Component/ES/QueuePriorityEnum.phpTrginusWorker/aTrai+: Phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "o Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:47:24Describe what you are looking for# releases8 22•MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70899
|
2545
|
44
|
2026-05-22T10:47:24.476918+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446844476_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormINavicarecodeKeractorFV faVsco.js°9 master PhostormINavicarecodeKeractorFV faVsco.js°9 master kroledey© InsightsService.php© NumberAllocator.php1extmessagingservice.ongc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilot0 WebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.php@ InsiahtSeatService.ohn© InstantMeetingService.php© IntercomService.php© IpapiClient.php© IpapiService.php© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorInterface.php© ResolveTeamCrmConnection.php© SimpleThrottleService.php@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.phpc Userservice.onrConvertLeadActivities.phpC) SaveActivity.pnpsynerorlannatpnp© AcuvityLeadConvertea.php©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C) AskAnythingPromptService.pho© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4NLAY70 (61pubulc tunccion -_conscrucelprivate readonly AutomatedReportsRepository $automatedReportsRepository.privace readonly Aucomareckeporcsservice saucomareakeportsservice.private readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vilautomated-reports/interest 1 usagepublic function trackInterest(Request Srequest): JsonResponsef...,* othrows AoplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuid123 19)public function delete(Request Srequest, string $uuid): JsonResponsel...*Local changesConsole xLog XО. л> sunc vavoook caregores ana crm tieia vatues lu tues. yesteroay 14?u4> Scim provisionina with rioles 8 files. 19.05.26. 11:06]→alDo not ignoreHighlight words 13 B ?• base version app/componvuse Jiminny Excentions LogicExcention:v CrmownerResolver 2l SoftPhoneManager.php app/Component/Twilio/Conference/ConferenceManag-PнE TextMessagingService.php app/Services/TelephonyJY-20991 SMS Teyt relav FU 2 files 15.05.26. 14:05use Jiminny Models.use Jiminny Models Particinant Connection:use Jiminny Services \ActivityService;use Jiminny Services Crm. cachedcrmServicelecaraton:• ID & SendRenort.loh 2 files 13.05.26 12•281use Jiminny (Services\Crm \ProviderRegistry:› reorder calls 1 file, 11.05.26, 10:09use liminnv Services Telenhonv TwilioClientRuilden•Move non nadinator soarch calls to client-scoarch 1 file 11.05 26 10:07_use Psr\Log\LoggerInterface;) Almact rosdy ono calli ta ho toctad 9 filac 905 26 11-51hubenot rato limit tomn chockout 17 filac 9 05 26 11:19Scontact = null-cavod coarch dolata 1 filo 27 04 26 11.59Cetado - null•› expiration message 6 files, 27.04.26, 9:23Connontunitv = null.=custom.log= laravel.log« SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php X|# console [euyA console [STAGING]namespace Jininny services lelephony>use...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient stwilionlient = nuuupublic function __construct(private readonly ProviderRegistry SproviderRegistry.private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,21...}* Othrows ConfigurationExceptionpublic function setTeam(Team Steam): void{...}* dchrows Exceptionpublic function send(User $from, string Sto, string Smessage): MessageInstance(...}* dchrows w1l1oExceotzonpublic function redact(Activity Sactivitv): void!...}116 v1 V1 A V100% 2. Fri 22 May 13:47:24AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0..The file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnskealoneThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownCustomer" issues.Gal .Ask anything (&OL)2 differencas8 Shelved versionuse Jiminny Excentions LogicExcention:use Jiminny\Models;use Jiminnv\Models\ Particinant\ Connection:use Jiminny|Services\ActivityService;use Jiminnv Services Activitv CrmiwnerResol ver.luse liminnv Services Crm. CachedCrmSenviceleconaton•luse liminnv Services Crn. ProviderRegistny.luse liminnv|Services| Telenhonv| TwilioflientRuilden,luse Pen loa. I nngenIntenface.Ccontant = null+$stage = null;W Windsurf TeamcPo 4 spaces...
|
NULL
|
-5361604862076854067
|
NULL
|
click
|
ocr
|
NULL
|
PhostormINavicarecodeKeractorFV faVsco.js°9 master PhostormINavicarecodeKeractorFV faVsco.js°9 master kroledey© InsightsService.php© NumberAllocator.php1extmessagingservice.ongc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilot0 WebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.php@ InsiahtSeatService.ohn© InstantMeetingService.php© IntercomService.php© IpapiClient.php© IpapiService.php© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorInterface.php© ResolveTeamCrmConnection.php© SimpleThrottleService.php@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.phpc Userservice.onrConvertLeadActivities.phpC) SaveActivity.pnpsynerorlannatpnp© AcuvityLeadConvertea.php©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C) AskAnythingPromptService.pho© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4NLAY70 (61pubulc tunccion -_conscrucelprivate readonly AutomatedReportsRepository $automatedReportsRepository.privace readonly Aucomareckeporcsservice saucomareakeportsservice.private readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vilautomated-reports/interest 1 usagepublic function trackInterest(Request Srequest): JsonResponsef...,* othrows AoplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuid123 19)public function delete(Request Srequest, string $uuid): JsonResponsel...*Local changesConsole xLog XО. л> sunc vavoook caregores ana crm tieia vatues lu tues. yesteroay 14?u4> Scim provisionina with rioles 8 files. 19.05.26. 11:06]→alDo not ignoreHighlight words 13 B ?• base version app/componvuse Jiminny Excentions LogicExcention:v CrmownerResolver 2l SoftPhoneManager.php app/Component/Twilio/Conference/ConferenceManag-PнE TextMessagingService.php app/Services/TelephonyJY-20991 SMS Teyt relav FU 2 files 15.05.26. 14:05use Jiminny Models.use Jiminny Models Particinant Connection:use Jiminny Services \ActivityService;use Jiminny Services Crm. cachedcrmServicelecaraton:• ID & SendRenort.loh 2 files 13.05.26 12•281use Jiminny (Services\Crm \ProviderRegistry:› reorder calls 1 file, 11.05.26, 10:09use liminnv Services Telenhonv TwilioClientRuilden•Move non nadinator soarch calls to client-scoarch 1 file 11.05 26 10:07_use Psr\Log\LoggerInterface;) Almact rosdy ono calli ta ho toctad 9 filac 905 26 11-51hubenot rato limit tomn chockout 17 filac 9 05 26 11:19Scontact = null-cavod coarch dolata 1 filo 27 04 26 11.59Cetado - null•› expiration message 6 files, 27.04.26, 9:23Connontunitv = null.=custom.log= laravel.log« SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php X|# console [euyA console [STAGING]namespace Jininny services lelephony>use...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient stwilionlient = nuuupublic function __construct(private readonly ProviderRegistry SproviderRegistry.private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,21...}* Othrows ConfigurationExceptionpublic function setTeam(Team Steam): void{...}* dchrows Exceptionpublic function send(User $from, string Sto, string Smessage): MessageInstance(...}* dchrows w1l1oExceotzonpublic function redact(Activity Sactivitv): void!...}116 v1 V1 A V100% 2. Fri 22 May 13:47:24AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0..The file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnskealoneThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownCustomer" issues.Gal .Ask anything (&OL)2 differencas8 Shelved versionuse Jiminny Excentions LogicExcention:use Jiminny\Models;use Jiminnv\Models\ Particinant\ Connection:use Jiminny|Services\ActivityService;use Jiminnv Services Activitv CrmiwnerResol ver.luse liminnv Services Crm. CachedCrmSenviceleconaton•luse liminnv Services Crn. ProviderRegistny.luse liminnv|Services| Telenhonv| TwilioflientRuilden,luse Pen loa. I nngenIntenface.Ccontant = null+$stage = null;W Windsurf TeamcPo 4 spaces...
|
70895
|
NULL
|
NULL
|
NULL
|
|
70897
|
2544
|
36
|
2026-05-22T10:47:18.243737+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446838243_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
16
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4356225183775441222
|
-4492964828840617980
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
16
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70896
|
NULL
|
NULL
|
NULL
|
|
70895
|
2545
|
43
|
2026-05-22T10:47:16.658281+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446836658_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
16
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16","depth":4,"bounds":{"left":0.69148934,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.30319148,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4356225183775441222
|
-4492964828840617980
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
16
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70896
|
2544
|
35
|
2026-05-22T10:47:14.491924+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446834491_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)O $82APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete modecreate mode 100644 app/Component/ES/QueuePriorityEnum.phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-ent|docker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "R. Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:47:14Describe what you are looking for# releases8 22• MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
6637770892061716400
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)O $82APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete modecreate mode 100644 app/Component/ES/QueuePriorityEnum.phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-ent|docker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "R. Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:47:14Describe what you are looking for# releases8 22• MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70894
|
2545
|
42
|
2026-05-22T10:47:13.470879+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446833470_m2.jpg...
|
iTerm2
|
NULL
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormINavicarecodeFV faVsco.js°9 master krolede PhostormINavicarecodeFV faVsco.js°9 master kroledey© InsightsService.php© NumberAllocator.php1extmessagingservice.ongc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilotWebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.phpC)InsiahtSeatService.oho© InstantMeetingService.php© IntercomService.php© IpapiClient.php© IpapiService.php© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorinterface.php@) PocolveTeamCrmConnection.onp© SimpleThrottleService.php(@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.phpc Userservice.onrConvertLeadActivities.phpC) SaveActivity.pnpsynerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4NLAY7а (61pubulc tunccion -_conscrucelprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeporcsservice saucomareakeportsserviceprivate readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vilautomated-reports/interest 1 usagepublic function trackInterest(Request Srequest): JsonResponsef...?* othrows AoplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuid123 1 >public function delete(Request Srequest, string $uuid): JsonResponset...*Local changesConsole xLog x +> sunc vavoook caregores ana crm tieia vatues lu tues. yesteroay 14?u4> Scim provisionina with rioles 8 files. 19.05.26. 11:06]CrmownerResolver 1 fles. 19.05.26. 11:05|• JY-20801 SMS Teyt relav 5U 3 fles. 15.05.26. 14:05.• ID & SendRenort.loh 2 files 13.05.26 12•39• reorder calls 1 file 11.05.26 10:00Move non naginator soarch calls to client-scearch 1 file 11.05.26 10:07• Almost ready one call to he tected 8 files 8.05 26 14•54hubsnot rate limit temn checkout 17 files 8.05 26 11:48cavad conrch doloto 1 fila 07 04 26 14.59› expiration message 6 files, 27.04.26, 9:23> UP dev loas 1 file, 24.04.26, 12:02• tom toct 1 4ilo 2404 26 10.26|Vorkcnace accociated with hranch 'macter' hac heon rectored Il Pollback II Confiaure (2 minutes ado)=custom.log= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php X A console [EU]A console [STAGING]namespace Jininny services lelephony>use...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient Stwilionlient = nultpublic function __construct(private readonly ProviderRegistry $providerRegistry,private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,21...}* Othrows ConfigurationExceptionpublic function setTeam(Team Steam): void{...}* dchrows Exceptionpublic function send(User $from, string Sto, string Smessage): MessageInstance(...}* Athrows TwilioExceptionpublic function redact(Activity Sactivitv): void!...}Nothing to show100% 2. Fri 22 May 13:47:13116 v1 V1 A VAskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token FallbThe file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnckcalonThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownCustomer" issues.Ask anything (&OL)W Windsurf Team1-1UTE.8IPo 4 spaces...
|
NULL
|
-6082681222406891638
|
NULL
|
click
|
ocr
|
NULL
|
PhostormINavicarecodeFV faVsco.js°9 master krolede PhostormINavicarecodeFV faVsco.js°9 master kroledey© InsightsService.php© NumberAllocator.php1extmessagingservice.ongc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilotWebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.phpC)InsiahtSeatService.oho© InstantMeetingService.php© IntercomService.php© IpapiClient.php© IpapiService.php© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorinterface.php@) PocolveTeamCrmConnection.onp© SimpleThrottleService.php(@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.phpc Userservice.onrConvertLeadActivities.phpC) SaveActivity.pnpsynerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4NLAY7а (61pubulc tunccion -_conscrucelprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeporcsservice saucomareakeportsserviceprivate readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vilautomated-reports/interest 1 usagepublic function trackInterest(Request Srequest): JsonResponsef...?* othrows AoplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuid123 1 >public function delete(Request Srequest, string $uuid): JsonResponset...*Local changesConsole xLog x +> sunc vavoook caregores ana crm tieia vatues lu tues. yesteroay 14?u4> Scim provisionina with rioles 8 files. 19.05.26. 11:06]CrmownerResolver 1 fles. 19.05.26. 11:05|• JY-20801 SMS Teyt relav 5U 3 fles. 15.05.26. 14:05.• ID & SendRenort.loh 2 files 13.05.26 12•39• reorder calls 1 file 11.05.26 10:00Move non naginator soarch calls to client-scearch 1 file 11.05.26 10:07• Almost ready one call to he tected 8 files 8.05 26 14•54hubsnot rate limit temn checkout 17 files 8.05 26 11:48cavad conrch doloto 1 fila 07 04 26 14.59› expiration message 6 files, 27.04.26, 9:23> UP dev loas 1 file, 24.04.26, 12:02• tom toct 1 4ilo 2404 26 10.26|Vorkcnace accociated with hranch 'macter' hac heon rectored Il Pollback II Confiaure (2 minutes ado)=custom.log= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php X A console [EU]A console [STAGING]namespace Jininny services lelephony>use...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient Stwilionlient = nultpublic function __construct(private readonly ProviderRegistry $providerRegistry,private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,21...}* Othrows ConfigurationExceptionpublic function setTeam(Team Steam): void{...}* dchrows Exceptionpublic function send(User $from, string Sto, string Smessage): MessageInstance(...}* Athrows TwilioExceptionpublic function redact(Activity Sactivitv): void!...}Nothing to show100% 2. Fri 22 May 13:47:13116 v1 V1 A VAskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token FallbThe file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnckcalonThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownCustomer" issues.Ask anything (&OL)W Windsurf Team1-1UTE.8IPo 4 spaces...
|
70892
|
NULL
|
NULL
|
NULL
|
|
70893
|
2544
|
34
|
2026-05-22T10:47:13.360466+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446833360_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
16
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"16","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4356225183775441222
|
-4492964828840617980
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
16
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
70890
|
NULL
|
NULL
|
NULL
|
|
70892
|
2545
|
41
|
2026-05-22T10:47:09.318390+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446829318_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
12
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
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
DealRisks
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
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, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.4119016,"top":0.24740623,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.4192154,"top":0.24740623,"width":0.006981383,"height":0.018355945},"on_screen":true,"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\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Http\\Controllers\\API\\UserAutomatedReports;\n\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Http\\JsonResponse;\nuse Jiminny\\Exceptions\\ApplicationException;\nuse Jiminny\\Http\\Controllers\\Controller;\nuse Jiminny\\Http\\Responses\\Api\\Response;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Repositories\\AutomatedReportsRepository;\nuse Jiminny\\Services\\ApiResponseService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSort;\nuse Jiminny\\Services\\Kiosk\\AutomatedReports\\ReportSortDirection;\nuse Jiminny\\Services\\PlanhatService;\nuse Illuminate\\Http\\Request;\nuse Throwable;\n\nclass UserAutomatedReportsController extends Controller\n{\n public const int RESULTS_PER_PAGE = 25;\n\n public const string SORT_COLUMN = 'sort_column';\n\n public const string SORT_DIRECTION = 'sort_direction';\n\n public function __construct(\n private readonly AutomatedReportsRepository $automatedReportsRepository,\n private readonly AutomatedReportsService $automatedReportsService,\n private readonly ApiResponseService $apiResponseService,\n private readonly Response $response,\n private readonly PlanhatService $planhatService,\n ) {\n parent::__construct();\n }\n\n public function trackInterest(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n defer(\n fn () => $this->planhatService->track(\n user: $user,\n event: 'automated-reports-track-interest',\n )\n )->always();\n\n return $this->response->withOk();\n }\n\n /**\n * @throws ApplicationException\n */\n public function list(Request $request): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n $teamIds = $request->has('team')\n ? (array) $request->get('team')\n : [];\n $reportTypes = $request->has('report_type')\n ? (array) $request->get('report_type')\n : [];\n $name = $request->has('name') ? trim($request->get('name', '')) : null;\n\n try {\n $fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;\n $toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;\n } catch (\\Exception) {\n return $this->response->errorWrongArgs('Invalid date.');\n }\n\n $page = $request->has('page') ? (int) $request->get('page') : 1;\n $sort = ReportSort::tryFrom(\n $request->get(self::SORT_COLUMN, '')\n ) ?? ReportSort::GENERATED_AT;\n $sortDirection = ReportSortDirection::tryFrom(\n strtolower($request->get(self::SORT_DIRECTION, ''))\n ) ?? ReportSortDirection::DESC;\n\n $paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(\n user: $user,\n sort: $sort,\n sortDirection: $sortDirection,\n resultsPerPage: self::RESULTS_PER_PAGE,\n page: $page,\n fromDate: $fromDate,\n toDate: $toDate,\n teamIds: array_map('intval', $teamIds),\n reportTypes: $reportTypes,\n name: $name,\n );\n\n $reportResults = $this->automatedReportsService->transformReportResults(\n $paginatedUserReports->getCollection()\n );\n $team = $user->getTeam();\n $reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(\n shortVersion: true,\n team: $team\n );\n\n $data = $this->apiResponseService->fromPaginatorToArray(\n paginator: $paginatedUserReports,\n data: $reportResults,\n moreMeta: [\n self::SORT_COLUMN => $sort->value,\n self::SORT_DIRECTION => $sortDirection->value,\n ],\n filters: [\n $reportTypeFilter['id'] => $reportTypeFilter,\n ],\n );\n\n return $this->response->withArray($data);\n }\n\n public function delete(Request $request, string $uuid): JsonResponse\n {\n /** @var User $user */\n $user = $request->user();\n\n try {\n $result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);\n\n if ($result === null) {\n return new JsonResponse(\n data: ['error' => 'Report not found'],\n status: JsonResponse::HTTP_NOT_FOUND\n );\n }\n\n $result->delete();\n\n return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);\n } catch (Throwable $e) {\n return new JsonResponse(\n data: ['error' => 'Failed to delete report result'],\n status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR\n );\n }\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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"12","depth":4,"bounds":{"left":0.69148934,"top":0.10055866,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.703125,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.7124335,"top":0.10055866,"width":0.00731383,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.72140956,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"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.7287234,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\n }\n}","depth":4,"bounds":{"left":0.4481383,"top":0.09736632,"width":0.30319148,"height":0.90263367},"on_screen":true,"value":"<?php\n\nnamespace Jiminny\\Services\\Telephony;\n\nuse Jiminny\\Component\\Activity\\Services\\GetDefaultActivityTypeService;\nuse Jiminny\\Component\\UrlGenerator\\Webhook;\nuse Jiminny\\Exceptions\\LogicException;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\PlaybookCategory;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Models\\User;\nuse Jiminny\\Services\\ActivityService;\nuse Jiminny\\Services\\Crm\\CachedCrmServiceDecorator;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Twilio\\Exceptions\\ConfigurationException;\nuse Twilio\\Exceptions\\TwilioException;\nuse Twilio\\Rest\\Api\\V2010\\Account\\MessageInstance;\n\nclass TextMessagingService\n{\n private ?Team $team = null;\n private ?TwilioClient $twilioClient = null;\n\n public function __construct(\n private readonly ProviderRegistry $providerRegistry,\n private readonly ActivityService $activityService,\n private readonly Webhook $urlGenerator,\n private readonly TwilioClientBuilder $twilioClientBuilder,\n ) {\n }\n\n /**\n * @throws ConfigurationException\n */\n public function setTeam(Team $team): void\n {\n $this->team = $team;\n $this->twilioClient = $this->twilioClientBuilder->build($team);\n }\n\n /**\n * @throws \\Exception\n */\n public function send(User $from, string $to, string $message): MessageInstance\n {\n if ($this->team->id !== $from->team_id) {\n throw new \\InvalidArgumentException('Sender does not belong to this team.');\n }\n\n $payload = [\n 'from' => $from->softphone_number,\n 'body' => $message,\n 'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.\n 'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),\n ];\n\n if ($this->team->twilio_messaging_sid !== null) {\n $payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;\n } else {\n $payload['applicationSid'] = $this->team->twilio_sms_sid;\n }\n\n return $this->twilioClient\n ->messages\n ->create($to, $payload);\n }\n\n /**\n * @throws TwilioException\n */\n public function redact(Activity $activity): void\n {\n $this->twilioClient\n ->messages($activity->telephony_provider_id)\n ->delete();\n }\n\n public function buildActivity(\n ?string $messageId,\n string $type,\n User $user,\n string $from,\n string $to,\n string $message,\n ?string $customerId,\n ?string $objectType,\n ?string $countryCode\n ): Activity {\n $lead = null;\n $account = null;\n $contact = null;\n $stage = null;\n $opportunity = null;\n\n $team = $user->getTeam();\n\n try {\n $crmService = $this->providerRegistry->get($team->crm->provider);\n\n if (! $user->isCrmRequired()) {\n $crmService->setUser($team->getOwner());\n } else {\n $crmService->setUser($user);\n }\n\n if ($customerId) {\n // Query the CRM for full details of this customer.\n [$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);\n\n // Prefer the passed country code from Twilio over CRM data.\n if ($countryCode === null && $crmCountryCode) {\n $countryCode = $crmCountryCode;\n }\n } else {\n $decorator = resolve(CachedCrmServiceDecorator::class);\n $decorator->setCrmService($crmService);\n\n // Lookup who the message might be to/from based on their phone number.\n $lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;\n [$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(\n $lookup,\n null,\n $user->getId()\n );\n }\n\n if ($opportunity === null) {\n $opportunities = $crmService->findOpportunities(\n $account ? $account->crm_provider_id : null,\n $contact ? $contact->crm_provider_id : null,\n $user->getId()\n );\n\n if (! empty($opportunities)) {\n $opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);\n }\n }\n } catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {\n // If their CRM has become disconnected, we really have no idea who the text customer is.\n $lead = $account = $opportunity = $contact = $stage = null;\n }\n\n if ($countryCode === null) {\n $countryCode = $user->country_code;\n }\n\n if ($type === Activity::TYPE_SMS_OUTBOUND) {\n $to = phone_e164($countryCode, $to);\n\n // Check they are allowed to text this number.\n if ($this->activityService->userCanText($user, $to, $countryCode) === false) {\n throw new \\InvalidArgumentException('Recipient in block list for this user.');\n }\n\n $title = 'Text to ' . phone_national($countryCode, $to);\n $status = Activity::STATUS_QUEUED;\n } else {\n $title = 'Text from ' . phone_national($countryCode, $from);\n $status = Activity::STATUS_RECEIVED;\n }\n\n $category = $this->getActivityType($user, $type);\n\n $activity = $user->activities()->create([\n 'provider' => Activity::PROVIDER_TWILIO,\n 'telephony_provider_id' => $messageId ?? null,\n 'type' => $type,\n 'title' => $title,\n 'description' => $message,\n 'crm_configuration_id' => $team->crm_id,\n 'playbook_category_id' => $category->id ?? null,\n 'lead_id' => $lead->id ?? null,\n 'account_id' => $account->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'value' => $opportunity ? $opportunity->value : null,\n 'stage_id' => $stage->id ?? null,\n 'status' => $status,\n 'scheduled_start_time' => now(),\n ]);\n\n if (! $activity instanceof Activity) {\n throw new LogicException('Activity model expected');\n }\n\n // Create two participants to store the sender/receiver of the message.\n $userParticipant = $activity->participants()->create([\n 'user_id' => $user->id,\n 'email' => $user->email,\n 'name' => $user->name,\n 'phone_number' => $user->softphone_number,\n ]);\n\n $prospectParticipant = $activity->participants()->create([\n 'lead_id' => $activity->lead_id ?? null,\n 'contact_id' => $activity->contact_id ?? null,\n 'name' => mb_strimwidth($activity->prospect_name, 0, 100),\n 'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,\n ]);\n\n // Refresh the model to fetch participants we just created so they'll be pushed to ES\n $activity->refresh();\n\n // Depending on the message direction, setup the from/to.\n $activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $userParticipant->id\n : $prospectParticipant->id;\n\n $activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND\n ? $prospectParticipant->id\n : $userParticipant->id;\n\n $activity->save();\n\n return $activity;\n }\n\n public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string\n {\n $crmService = $this->providerRegistry->get($user->team->crm->provider);\n $crmService->setUser($user);\n\n // Query the CRM for full details of this customer.\n try {\n [, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);\n } catch (\\Exception $ex) {\n \\Sentry::captureException($ex);\n\n return $user->getCountryCode();\n }\n\n // Prefer the passed country code from CRM data to the user.\n if ($countryCode === null) {\n $countryCode = $user->getCountryCode();\n }\n\n return $countryCode;\n }\n\n private function getActivityType(User $user, string $type): ?PlaybookCategory\n {\n $getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);\n\n return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);\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,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"app ~/jiminny/app","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"app, sources root","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Actions","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ElasticSearch","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Eloquent","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encoding","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Encryption","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Faker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Model, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Notification, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Nudge, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParagraphBreaker, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ParticipantSpeech, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PartitionedCookie, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackPage, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TranscriptionSummary, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uploader, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UrlGenerator, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Utility, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Uuid, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Waveform, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Webhooks, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Workflow, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Configuration","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Commands, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activities, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":10,"on_screen":false,"role_description":"text"}]...
|
8170209268239882235
|
-3340041108030638074
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\UserAutomatedReports;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Http\Controllers\Controller;
use Jiminny\Http\Responses\Api\Response;
use Jiminny\Models\User;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\ApiResponseService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSort;
use Jiminny\Services\Kiosk\AutomatedReports\ReportSortDirection;
use Jiminny\Services\PlanhatService;
use Illuminate\Http\Request;
use Throwable;
class UserAutomatedReportsController extends Controller
{
public const int RESULTS_PER_PAGE = 25;
public const string SORT_COLUMN = 'sort_column';
public const string SORT_DIRECTION = 'sort_direction';
public function __construct(
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly AutomatedReportsService $automatedReportsService,
private readonly ApiResponseService $apiResponseService,
private readonly Response $response,
private readonly PlanhatService $planhatService,
) {
parent::__construct();
}
public function trackInterest(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
defer(
fn () => $this->planhatService->track(
user: $user,
event: 'automated-reports-track-interest',
)
)->always();
return $this->response->withOk();
}
/**
* @throws ApplicationException
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$teamIds = $request->has('team')
? (array) $request->get('team')
: [];
$reportTypes = $request->has('report_type')
? (array) $request->get('report_type')
: [];
$name = $request->has('name') ? trim($request->get('name', '')) : null;
try {
$fromDate = $request->has('from_date') ? Carbon::parse($request->get('from_date')) : null;
$toDate = $request->has('to_date') ? Carbon::parse($request->get('to_date')) : null;
} catch (\Exception) {
return $this->response->errorWrongArgs('Invalid date.');
}
$page = $request->has('page') ? (int) $request->get('page') : 1;
$sort = ReportSort::tryFrom(
$request->get(self::SORT_COLUMN, '')
) ?? ReportSort::GENERATED_AT;
$sortDirection = ReportSortDirection::tryFrom(
strtolower($request->get(self::SORT_DIRECTION, ''))
) ?? ReportSortDirection::DESC;
$paginatedUserReports = $this->automatedReportsRepository->getPaginatedUserReports(
user: $user,
sort: $sort,
sortDirection: $sortDirection,
resultsPerPage: self::RESULTS_PER_PAGE,
page: $page,
fromDate: $fromDate,
toDate: $toDate,
teamIds: array_map('intval', $teamIds),
reportTypes: $reportTypes,
name: $name,
);
$reportResults = $this->automatedReportsService->transformReportResults(
$paginatedUserReports->getCollection()
);
$team = $user->getTeam();
$reportTypeFilter = $this->automatedReportsService->getReportTypeFieldData(
shortVersion: true,
team: $team
);
$data = $this->apiResponseService->fromPaginatorToArray(
paginator: $paginatedUserReports,
data: $reportResults,
moreMeta: [
self::SORT_COLUMN => $sort->value,
self::SORT_DIRECTION => $sortDirection->value,
],
filters: [
$reportTypeFilter['id'] => $reportTypeFilter,
],
);
return $this->response->withArray($data);
}
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$result = $this->automatedReportsRepository->findResultByUuidForUser($uuid, $user);
if ($result === null) {
return new JsonResponse(
data: ['error' => 'Report not found'],
status: JsonResponse::HTTP_NOT_FOUND
);
}
$result->delete();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
} catch (Throwable $e) {
return new JsonResponse(
data: ['error' => 'Failed to delete report result'],
status: JsonResponse::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
12
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Services\Telephony;
use Jiminny\Component\Activity\Services\GetDefaultActivityTypeService;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Exceptions\LogicException;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\PlaybookCategory;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\ActivityService;
use Jiminny\Services\Crm\CachedCrmServiceDecorator;
use Jiminny\Services\Crm\ProviderRegistry;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Api\V2010\Account\MessageInstance;
class TextMessagingService
{
private ?Team $team = null;
private ?TwilioClient $twilioClient = null;
public function __construct(
private readonly ProviderRegistry $providerRegistry,
private readonly ActivityService $activityService,
private readonly Webhook $urlGenerator,
private readonly TwilioClientBuilder $twilioClientBuilder,
) {
}
/**
* @throws ConfigurationException
*/
public function setTeam(Team $team): void
{
$this->team = $team;
$this->twilioClient = $this->twilioClientBuilder->build($team);
}
/**
* @throws \Exception
*/
public function send(User $from, string $to, string $message): MessageInstance
{
if ($this->team->id !== $from->team_id) {
throw new \InvalidArgumentException('Sender does not belong to this team.');
}
$payload = [
'from' => $from->softphone_number,
'body' => $message,
'smartEncoded' => true, // Detect Unicode characters that have a similar GSM-7 character and replace.
'statusCallback' => $this->urlGenerator->route('jiminny.webhook.softphone.sms.event'),
];
if ($this->team->twilio_messaging_sid !== null) {
$payload['messagingServiceSid'] = $this->team->twilio_messaging_sid;
} else {
$payload['applicationSid'] = $this->team->twilio_sms_sid;
}
return $this->twilioClient
->messages
->create($to, $payload);
}
/**
* @throws TwilioException
*/
public function redact(Activity $activity): void
{
$this->twilioClient
->messages($activity->telephony_provider_id)
->delete();
}
public function buildActivity(
?string $messageId,
string $type,
User $user,
string $from,
string $to,
string $message,
?string $customerId,
?string $objectType,
?string $countryCode
): Activity {
$lead = null;
$account = null;
$contact = null;
$stage = null;
$opportunity = null;
$team = $user->getTeam();
try {
$crmService = $this->providerRegistry->get($team->crm->provider);
if (! $user->isCrmRequired()) {
$crmService->setUser($team->getOwner());
} else {
$crmService->setUser($user);
}
if ($customerId) {
// Query the CRM for full details of this customer.
[$lead, $account, $opportunity, $contact, $stage, $crmCountryCode] = $crmService->parseRecords($customerId, $objectType);
// Prefer the passed country code from Twilio over CRM data.
if ($countryCode === null && $crmCountryCode) {
$countryCode = $crmCountryCode;
}
} else {
$decorator = resolve(CachedCrmServiceDecorator::class);
$decorator->setCrmService($crmService);
// Lookup who the message might be to/from based on their phone number.
$lookup = $type === Activity::TYPE_SMS_INBOUND ? $from : $to;
[$lead, $account, $opportunity, $contact, $stage] = $decorator->matchByPhone(
$lookup,
null,
$user->getId()
);
}
if ($opportunity === null) {
$opportunities = $crmService->findOpportunities(
$account ? $account->crm_provider_id : null,
$contact ? $contact->crm_provider_id : null,
$user->getId()
);
if (! empty($opportunities)) {
$opportunity = $crmService->syncOpportunity($opportunities[0]['crmId']);
}
}
} catch (SocialAccountTokenInvalidException $accountTokenInvalidException) {
// If their CRM has become disconnected, we really have no idea who the text customer is.
$lead = $account = $opportunity = $contact = $stage = null;
}
if ($countryCode === null) {
$countryCode = $user->country_code;
}
if ($type === Activity::TYPE_SMS_OUTBOUND) {
$to = phone_e164($countryCode, $to);
// Check they are allowed to text this number.
if ($this->activityService->userCanText($user, $to, $countryCode) === false) {
throw new \InvalidArgumentException('Recipient in block list for this user.');
}
$title = 'Text to ' . phone_national($countryCode, $to);
$status = Activity::STATUS_QUEUED;
} else {
$title = 'Text from ' . phone_national($countryCode, $from);
$status = Activity::STATUS_RECEIVED;
}
$category = $this->getActivityType($user, $type);
$activity = $user->activities()->create([
'provider' => Activity::PROVIDER_TWILIO,
'telephony_provider_id' => $messageId ?? null,
'type' => $type,
'title' => $title,
'description' => $message,
'crm_configuration_id' => $team->crm_id,
'playbook_category_id' => $category->id ?? null,
'lead_id' => $lead->id ?? null,
'account_id' => $account->id ?? null,
'contact_id' => $contact->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'value' => $opportunity ? $opportunity->value : null,
'stage_id' => $stage->id ?? null,
'status' => $status,
'scheduled_start_time' => now(),
]);
if (! $activity instanceof Activity) {
throw new LogicException('Activity model expected');
}
// Create two participants to store the sender/receiver of the message.
$userParticipant = $activity->participants()->create([
'user_id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'phone_number' => $user->softphone_number,
]);
$prospectParticipant = $activity->participants()->create([
'lead_id' => $activity->lead_id ?? null,
'contact_id' => $activity->contact_id ?? null,
'name' => mb_strimwidth($activity->prospect_name, 0, 100),
'phone_number' => $type === Activity::TYPE_SMS_OUTBOUND ? $to : $from,
]);
// Refresh the model to fetch participants we just created so they'll be pushed to ES
$activity->refresh();
// Depending on the message direction, setup the from/to.
$activity->from_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $userParticipant->id
: $prospectParticipant->id;
$activity->to_participant_id = $type === Activity::TYPE_SMS_OUTBOUND
? $prospectParticipant->id
: $userParticipant->id;
$activity->save();
return $activity;
}
public function convertCustomerToCountryCode(User $user, string $customerId, ?string $objectType): string
{
$crmService = $this->providerRegistry->get($user->team->crm->provider);
$crmService->setUser($user);
// Query the CRM for full details of this customer.
try {
[, , , , , $countryCode] = $crmService->parseRecords($customerId, $objectType);
} catch (\Exception $ex) {
\Sentry::captureException($ex);
return $user->getCountryCode();
}
// Prefer the passed country code from CRM data to the user.
if ($countryCode === null) {
$countryCode = $user->getCountryCode();
}
return $countryCode;
}
private function getActivityType(User $user, string $type): ?PlaybookCategory
{
$getDefaultActivityTypeService = app(GetDefaultActivityTypeService::class);
return $getDefaultActivityTypeService->getForUserAndChannel($user, $type);
}
}
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
DealRisks
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker, folder
FeatureFlags, folder
FFMpeg, folder
FileSystem, folder
Gecko, folder
Gong, folder
GuzzleHttp, folder
KeyPoints, folder
Kiosk, folder
LanguageDetection, folder
LiveFeed, folder
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, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70891
|
2545
|
40
|
2026-05-22T10:47:05.468193+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446825468_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormFV faVsco.jsroledey© InsightsService.phpNu PhostormFV faVsco.jsroledey© InsightsService.phpNumoerAllocator.onplextmessagingservice.onpc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilot_ WebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.phpC)InsiahtSeatService.oho© InstantMeetingService.php© IntercomService.php© IpapiClient.php(c) Inan Service nhnl© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorinterface.php@) PocolveTeamCrmConnection.onp© SimpleThrottleService.php@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.php58g >123 1 >c Userservice.onrLocal changesConsole xLog Xenv.local aon@.liminnvDehuaCommandinhn annlConcole/Commanddpip logging.onp contiv Unversioned Files 9 filesE.env.nikilocal app= .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.E favicon.ico publicE ids.txt apdTraw sal query.sal app© SimulateWebhooksCommand.php app/Console/Commands/Crm/HubspotM+ WEBHOOK FILTERING_IMPLEMENTATION.md apdConvertLeadActivities.phpC) SaveActivity.pnpsynerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4NLAY70 (61pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeportsservice saucomareakeportsserviceprivate readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request Srequest): JsonResponsef...?* othrows ApplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuidhpublic function delete(Request Srequest, string $uuid): JsonResponsel...}=custom.log= laravel.log« SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php XA console [EU]A console [STAGING]k?phpnamespace Jininny services lelephony>use...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient Stwilion ient = nulupublic function __construct(private readonly ProviderRegistry $providerRegistry,private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,21...}* Othrows ConfigurationExceptionpublic function setTeam(Team Steam): void{...}* Athrows Exceptionpublic function send(User $from, string Sto, string $message): MessageInstance(...}* Athrows TwilioExceptionpublic function redact(Activity Sactivitv): void!...}100% 52. Fri 22 May 13:47:05AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0 ..The file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrnskealoneFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownGal .Ask anything (&OL)+ → @ Side-by-side viewer ~Do not ianore v81aee7aad config/logging.php"driver' => 'errorlog'..'Level' => env('LOG_LEVEL', 'info'),"nath' => stonade nath('oas/anavel.iog')lXI B|?1 differencoCurrant vorcion'level' => env('LOG LEVEL', 'info').'path' => storage_path('logs/Laravel.log'),"custom channelt =>fdriver' => 'single','path' => storage_path('logs/custom.log'),'level' => env('LOG_LEVEL', 'info').W Windsurf Teamcio 4 spaces...
|
NULL
|
313135823754938254
|
NULL
|
click
|
ocr
|
NULL
|
PhostormFV faVsco.jsroledey© InsightsService.phpNu PhostormFV faVsco.jsroledey© InsightsService.phpNumoerAllocator.onplextmessagingservice.onpc I willoclient.onpcIwlloclientbullder.pnpcIwilloservice.ono© TwilioSubAccountCreator.php•D UserPilot_ WebhookC AbstractService.php© ActivitvProviderFactory.php©) [EMAIL]© ConferenceService.phpC)InsiahtSeatService.oho© InstantMeetingService.php© IntercomService.php© IpapiClient.php(c) Inan Service nhnl© ParticipantShareService.php@ PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.php© PlaybookCategoryService.php© PlaylistGeneratorinterface.php@) PocolveTeamCrmConnection.onp© SimpleThrottleService.php@ ClackService.php© SocialAccountService.php© SoftPhoneService.php© TeamDeactivatedService.php© TeamOwnerService.php© TeamService.php© TranscodeParameterResolver.php58g >123 1 >c Userservice.onrLocal changesConsole xLog Xenv.local aon@.liminnvDehuaCommandinhn annlConcole/Commanddpip logging.onp contiv Unversioned Files 9 filesE.env.nikilocal app= .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.E favicon.ico publicE ids.txt apdTraw sal query.sal app© SimulateWebhooksCommand.php app/Console/Commands/Crm/HubspotM+ WEBHOOK FILTERING_IMPLEMENTATION.md apdConvertLeadActivities.phpC) SaveActivity.pnpsynerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerBI4NLAY70 (61pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeportsservice saucomareakeportsserviceprivate readonly Apikesponseservice sapzkesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService,{...1PosTlaoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request Srequest): JsonResponsef...?* othrows ApplicationExceotionG-Taoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuidhpublic function delete(Request Srequest, string $uuid): JsonResponsel...}=custom.log= laravel.log« SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]© TextMessagingService.php XA console [EU]A console [STAGING]k?phpnamespace Jininny services lelephony>use...class TextMessaqngServiceorivate ?Team steam = nulu3 usadesorivate ?TwilioClient Stwilion ient = nulupublic function __construct(private readonly ProviderRegistry $providerRegistry,private readonly ActivityService SactivityService,private readonly Webhook SurlGeneratorprivate readonly TwilioClientBuilder $twilioClientBuilder,21...}* Othrows ConfigurationExceptionpublic function setTeam(Team Steam): void{...}* Athrows Exceptionpublic function send(User $from, string Sto, string $message): MessageInstance(...}* Athrows TwilioExceptionpublic function redact(Activity Sactivitv): void!...}100% 52. Fri 22 May 13:47:05AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0 ..The file is still off lot of warning just see what is wrongThoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrnskealoneFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added the1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now:• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownGal .Ask anything (&OL)+ → @ Side-by-side viewer ~Do not ianore v81aee7aad config/logging.php"driver' => 'errorlog'..'Level' => env('LOG_LEVEL', 'info'),"nath' => stonade nath('oas/anavel.iog')lXI B|?1 differencoCurrant vorcion'level' => env('LOG LEVEL', 'info').'path' => storage_path('logs/Laravel.log'),"custom channelt =>fdriver' => 'single','path' => storage_path('logs/custom.log'),'level' => env('LOG_LEVEL', 'info').W Windsurf Teamcio 4 spaces...
|
70889
|
NULL
|
NULL
|
NULL
|
|
70890
|
2544
|
33
|
2026-05-22T10:47:05.361213+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446825361_m1.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)О 882APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete mode100644 app/Component/ES/Processor/Traits/WorkerIdTrait.phpcreate mode 100644 app/Component/ES/QueuePriorityEnum.phpTrginusWorker/aTrai+: Phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "R. Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:47:04Describe what you are looking for# releases8 22• MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
6774953836728576770
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)О 882APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete mode100644 app/Component/ES/Processor/Traits/WorkerIdTrait.phpcreate mode 100644 app/Component/ES/QueuePriorityEnum.phpTrginusWorker/aTrai+: Phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "R. Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:47:04Describe what you are looking for# releases8 22• MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70889
|
2545
|
39
|
2026-05-22T10:47:01.813556+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446821813_m2.jpg...
|
PhpStorm
|
faVsco.js – TextMessagingService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormVIewINavicarecodeFV faVsco.js?9 master kEi PhostormVIewINavicarecodeFV faVsco.js?9 master kEinfection.ison.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeMellicenses.moM MakefileJ package-lock.json= phpstan.neon.dist= phpstan-baseline.neon<> phpunit.xmlT%raw_sqL_query.sqMLDSADMS mdlsonar-project.properties= test.py‹› Untided Diagram.Xmiis vetur.config.isw+ WEBHOOKFILIERINGIMPLEMENTATION.md› ih External Librariesv E°Scratches and Consolesv M Database ConsolesVLEU& console lEUlA DEALRISKS EUIADIEUIAEU TEUv A liminnv@localhostconsole lliminnv@llocalhostiA DI lliminnv@localhost4 HS local fiminnv@localhost)l# SF fliminnv@localhost]Azoho dev fiminnv@localhostiV A PRODAconcale DRODIA console_1 [PROD]A DI (PRODIShelfiConsoleo synerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php(C) AutomatedReportsRepositorv.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerB14 XLA7а (61pubulc tunccion -_conscrucelprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeporcsservice saucomareakeportsservice.private readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService.{...1PosTlaoi/vllautomated-reoorts/interest usagepublic function trackinterestRequest Srequest: JsonResponse...* othrows AoplicationExceotionGsTaoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuidh123 1 >public function delete(Request Srequest, string $uuid): JsonResponsel...}Local ChangesLoa]E.env.local appTJ& + → Side-by-side viewer ~Do not ignore© JiminnyDebugCommand.php app/Console/Commandsolaeezaad confia/loaaina.phpUnversioned Files 9 files=.env.nikilocal app'level' => env('LOG_LEVEL', 'info'),E.env.other app'path' => storage_path('logs/laravel.log'),C) CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdioraw sal querv sall aorc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootMJ WEBHOOK FILTERING IMPLEMENTATION.md apd= laravel.log« SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]lextwessagingservice.onpx# console [euyA console [STAGING]k?phpnamespace Jininny services lelephonyuse Jiminny Component Activity Services betdetaultactivitylypeserviceuse Jiminny Exceptions Soc1alAccounttokenInval1dExcept1onsuse Jiminny Models Activity:use Jaminny Models Plavbookcatedory:use Jaminny Models Tieamuse Jiminny Models\User;use Jiminny Services ActivitvService:use Jiminny\Services\Crm \CachedCrmServiceDecorator:use Jiminnv Services Crm ProviderRegistry:use Twilio\Exceptions\ConfigurationException;use Twillin Sycentions Twil iosycention.use Twilio (Rest\Api\V2010\Account \MessageInstance;class TextMessagingserviceprivate ?Team $team = null;private ?TwilioClient StwilioClient = null:public function construct(private readonly Providerreqstry Sproviderkeq1stry.private readonly Activityservice Sactivityserviceorivate readonuy webhook surueneratorprivate readonly Twil1oCllentBualder StwillocllentBualder,100% 2. Fri 22 May 13:47:01U AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0 ..The file is still off lot of warning just see what is wrongTnoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnckcalonThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added therinal Implementation summary:1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now.• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownGal .Ask anything (NAL)1 dittoronsoleurrent version'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/laravel.log').cuctom channolt =s fl'driver' => 'single'.'path' => storage_path( 'logs/custom.log'),'level' => env('LOG LEVEL', 'info').W Windsurf Teams 594:60 UTF-8Aensod...
|
NULL
|
2973631445996521777
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormVIewINavicarecodeFV faVsco.js?9 master kEi PhostormVIewINavicarecodeFV faVsco.js?9 master kEinfection.ison.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeMellicenses.moM MakefileJ package-lock.json= phpstan.neon.dist= phpstan-baseline.neon<> phpunit.xmlT%raw_sqL_query.sqMLDSADMS mdlsonar-project.properties= test.py‹› Untided Diagram.Xmiis vetur.config.isw+ WEBHOOKFILIERINGIMPLEMENTATION.md› ih External Librariesv E°Scratches and Consolesv M Database ConsolesVLEU& console lEUlA DEALRISKS EUIADIEUIAEU TEUv A liminnv@localhostconsole lliminnv@llocalhostiA DI lliminnv@localhost4 HS local fiminnv@localhost)l# SF fliminnv@localhost]Azoho dev fiminnv@localhostiV A PRODAconcale DRODIA console_1 [PROD]A DI (PRODIShelfiConsoleo synerorlannatpnp©PlaybackController.phppnp api.phpC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho(C)AskAnythingPromptService.php(C) AutomatedReportsRepositorv.php(C) AutomatedReportsCommand.phpphp api v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends ControllerB14 XLA7а (61pubulc tunccion -_conscrucelprivate readonly AutomatedReportsRepository $automatedReportsRepositoryprivace readonly Aucomareckeporcsservice saucomareakeportsservice.private readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService $planhatService.{...1PosTlaoi/vllautomated-reoorts/interest usagepublic function trackinterestRequest Srequest: JsonResponse...* othrows AoplicationExceotionGsTaoi/vlautomated-renortsnublic function List(Request Srequest): IsonResnonsef.}DELETE lanilv1/automated-renorts/uuidh123 1 >public function delete(Request Srequest, string $uuid): JsonResponsel...}Local ChangesLoa]E.env.local appTJ& + → Side-by-side viewer ~Do not ignore© JiminnyDebugCommand.php app/Console/Commandsolaeezaad confia/loaaina.phpUnversioned Files 9 files=.env.nikilocal app'level' => env('LOG_LEVEL', 'info'),E.env.other app'path' => storage_path('logs/laravel.log'),C) CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnvReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdioraw sal querv sall aorc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootMJ WEBHOOK FILTERING IMPLEMENTATION.md apd= laravel.log« SF jiminny@localhost]4 HS_local [jiminny@localhost]& console [PROD]lextwessagingservice.onpx# console [euyA console [STAGING]k?phpnamespace Jininny services lelephonyuse Jiminny Component Activity Services betdetaultactivitylypeserviceuse Jiminny Exceptions Soc1alAccounttokenInval1dExcept1onsuse Jiminny Models Activity:use Jaminny Models Plavbookcatedory:use Jaminny Models Tieamuse Jiminny Models\User;use Jiminny Services ActivitvService:use Jiminny\Services\Crm \CachedCrmServiceDecorator:use Jiminnv Services Crm ProviderRegistry:use Twilio\Exceptions\ConfigurationException;use Twillin Sycentions Twil iosycention.use Twilio (Rest\Api\V2010\Account \MessageInstance;class TextMessagingserviceprivate ?Team $team = null;private ?TwilioClient StwilioClient = null:public function construct(private readonly Providerreqstry Sproviderkeq1stry.private readonly Activityservice Sactivityserviceorivate readonuy webhook surueneratorprivate readonly Twil1oCllentBualder StwillocllentBualder,100% 2. Fri 22 May 13:47:01U AskJiminnyReportActivityServiceTest vCascadeInvestigate Foreign KeSalestorce Token Fallb+0 ..The file is still off lot of warning just see what is wrongTnoughtsInought tor 1sI see the issue - the try-catch structure is broken. The catch block on line 147 for SocialAccountTokenInvalidExceptionhas no matching try block because the CRM usage code (lines 115-146) is not wrapped in a try block.• TextMessagingService.phrThoughtsnckcalonThoughtsFixed the trv-catch olock structure in TextMessagingService, The issue was that the catch olock tor Social AccountTokeninvalidException (line 147) had no matching try block - the CRM usage code wasn't wrapped in a try block after I added therinal Implementation summary:1. SoftPhoneManager.php - Added CrmOwnerResolver fallback for both outbound and inbound Twilio calls2. TextMessagingService.php - Added CrmOwnerResolver fallback for both buildActivity and convertCustomerToCouncrylode metnoas, with proper try-catch structureBoth imolementations now.• First try CrmOwnerResolver to find an active Salesforce token from the organization• If that fails, fall back to the original direct user assignment pattern• Let existing outer catch blocks handle any Socia AccountTokenInvalidException by settina CRM data to nul.This ensures Twilio calls will use another active Salesforce token when the admin token expires, preventing "UnknownGal .Ask anything (NAL)1 dittoronsoleurrent version'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/laravel.log').cuctom channolt =s fl'driver' => 'single'.'path' => storage_path( 'logs/custom.log'),'level' => env('LOG LEVEL', 'info').W Windsurf Teams 594:60 UTF-8Aensod...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70888
|
2544
|
32
|
2026-05-22T10:46:59.972539+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446819972_m1.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
8243381250999052583
|
-8204424741936591934
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)O $82APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete modecreate mode 100644 app/Component/ES/QueuePriorityEnum.phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-ent|docker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "o Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:46:59Describe what you are looking for# releases8 22•MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
70885
|
NULL
|
NULL
|
NULL
|
|
70887
|
2545
|
38
|
2026-05-22T10:46:57.771324+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446817771_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6716744925979352142
|
-8204420343923495994
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
PhostormFV faVsco.js~VIewg9 master kNavigateCodeE infection.json.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeM+licenses.mdM MakefileO package-lock.jsonE phpstan.neon.distE phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdtộ sonar-project.propertiesE test.py‹› Untided Diagram.XmiIS vetur.config.jsw+ WEBHOOKFILIERINGIMPLEMENTATION.md› ih External Librariesv E°Scratches and Consoles~ D Database ConsolesVLEU& console lEUlA DEALRISKS EUIADITEUIAEU TEUv A jiminny@localhostconsole lliminnv@llocalhostiDI jiminny@localhost]4 HS local fiminnv@localhost)#SF [jiminny@localhost]Azoho dev fiminnv@localhostiV A PRODA console [PROD]A console_1 [PROD]A DI [PROD]123 (Cg>Local ChangesShelfiConsoleLoa]E .env.local app© JiminnyDebugCommand.php app/Console/CommandsUnversioned Files 9 files=.env.nikilocal appE .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdTo raw_sql_query.sql appc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootM+ WEBHOOK_FILTERING_IMPLEMENTATION.md app) ConvertLeadActivities.phposynerorlannatonpPlaybackController.phppnp api.phgC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho© AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp apL_v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends Controller30 €pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepository,privace readonly Aucomareckeporcsservice saucomareakeportsservice.BI4NLAYprivate readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService SplanhatService,) 4.3PosTlaoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request $request): JsonResponsef...** othrows AoplicationExceotionG-Taoi/vlautomated-renortspublic function List(Request Srequest): JsonResponsef...7DELETE /api/v1/automated-reports/(uuid)public function delete(Request Srequest, string $uuid): JsonResponsef...}E custom.logA console [STAGING]E laravel.log4 SF [jiminny@localhost]A HS_Jocal [jiminny@localhost]A console [PROD] XA console (EU]CascadeInvestigate Foreign KeCascade580584587589590593594595596T597-598599600Tx: AutovCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE'' END) AS USEr_ifajiminny044 A1 A41 X66 ^SuiM Kole Management Implementationsa.*,t.owner_id FROM social_accounts saJOIN users u on U.id = sa.sociable_idJOIN teams t 1..n<-›1: on t.id = u.team_idWHERE u.team_id = 581 and sa.provider = 'salesforce':© Fixing Redis Rate Limit Error© Implement Trial Owner Role SelectionSELECT * FROM automated_report_results order by id desc;select * from features;select * from team_features where feature_id = 40;select * from teams where id = 556;select * trom automated reports;where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMautomated report resulrs Wiake 10 = 194191select * fromautomated report results WHERE report id = 54:select * from ooportunities where 1d = 75945491SELE * FROM teamsWHERE name LIKE "%lesh': # 711, 692. 16067-Timinnvintearationdlesmills.comselect * from playbooks where team_id = 711; # event 226147SELEd * FROM DLavbook catedonies WHERE DLavbook 10 = 5515SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event' ;SELECT * SROM eom fields WHERE1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;Hubspot Activity lype Discrepancy Investigation© Al Activity Type Flow ExplanationAsk anything (+L)SELECT * FROM crm_configurations WHERE id = 692;50 lalU AskJiminnyReportActivityServiceTest~100% L28• Fri 22 May 13:46:57+0 ..current workspace800Cascade Code *or.Kick off a new project. Make changesacross vour entire codebase36m22h1 dittoronsolTJ& < → Side-by-side viewer -olaeezaad confia/loaaina.phpDo not ignore'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('Logs/Laravel. Log'),eurrent version'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/Laravel. l0g'),'custom channel' »> ["driver' => 'single','path' => storage_path('logs/custom.1og'),'Level' => env('LOG_LEVEL', 'info'),W Windsurf Teams 594:60 UTF-84 spaces...
|
70886
|
NULL
|
NULL
|
NULL
|
|
70885
|
2544
|
31
|
2026-05-22T10:46:43.954930+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446803954_m1.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4235983745889776938
|
-8204421443435123770
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)O $82APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete modecreate mode 100644 app/Component/ES/QueuePriorityEnum.phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-ent|docker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "o Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:46:43Describe what you are looking for# releases8 22•MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70886
|
2545
|
37
|
2026-05-22T10:46:43.843726+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446803843_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"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},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"14","depth":4,"bounds":{"left":0.390625,"top":0.2490024,"width":0.009640957,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"2","depth":4,"bounds":{"left":0.40226063,"top":0.2490024,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"}]...
|
-1185626629069528695
|
-8204420481329034302
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
Sync Changes
Hide This Notification
Code changed:
Hide
14
2
PhostormFV faVsco.js~VIewg9 master kNavigateCodeE infection.json.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeM+licenses.mdM MakefileO package-lock.jsonE phpstan.neon.distE phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdtộ sonar-project.propertiesE test.py‹› Untided Diagram.XmiIS vetur.config.jsw+ WEBHOOKFILIERING_IMPLEMENTATION.md› ih External Librariesv E°Scratches and Consoles~ D Database ConsolesVLEU& console lEUl& DEALRISKS EUIADITEUIAEU TEUv A jiminny@localhostconsole lliminnv@llocalhostiDI jiminny@localhost]4 HS local fiminnv@localhost)#SF [jiminny@localhost]Azoho dev fiminnv@localhostiV A PRODA console [PROD]A console_1 [PROD]A DI [PROD]) ConvertLeadActivities.php© SaveActivity.phposynerorlannatonpPlaybackController.phppnp api.phgC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho© AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp apL_v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends Controller30 €pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepository,privace readonly Aucomareckeporcsservice saucomareakeportsservice.private readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService SplanhatService,) 4.3A14K2 A VPosT/aoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request $request): JsonResponsef...** othrows AoplicationExceotionG-Taoi/vlautomated-renortspublic function List(Request Srequest): JsonResponsef...7123 (Cg>DELETE /api/v1/automated-reports/(uuid)public function delete(Request Srequest, string $uuid): JsonResponsef...}Local ChangesShelfiConsoleLoa]E .env.local app© JiminnyDebugCommand.php app/Console/CommandsTJ& < → Side-by-side viewer -olaeezaad confia/loaaina.phpDo not ignoreUnversioned Files 9 files=.env.nikilocal appE .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdTo raw_sql_query.sql appc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootM+ WEBHOOK_FILTERING_IMPLEMENTATION.md app'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('Logs/Laravel. Log'),workspace associated with branch master nas been restored // kollback // Contigure... (a minute ago)E custom.logA console [STAGING]E laravel.log4 SF [jiminny@localhost]A HS_Jocal [jiminny@localhost]A console [PROD] x& console [EU]CascadeInvestigate Foreign Ke580584587589590593594595596T597-598599600Tx: AutovfajiminnyCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE' END) AS USer_14441841 X00 ^Ysa.*,t.owner_id FROM social_accounts saJOIN users u on U.id = sa.sociable_idJOIN teams t 1..n<-›1: on t.id = u.team_idWHERE u.team_id = 581 and sa.provider = 'salesforce':SELECT * FROM automated_report_results order by id desc;select * from features;select * from team_features where feature_id = 40;select * from teams where id = 556;select * trom automated reports;where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMautomated report resulrs Wiake 10 = 19191select * fromautomated report results WHERE report id = 54:select * from ooportunities where 1d = 75945491SELE * FROM teamsWHERE name LIKE "%lesh': # 711, 692. 16067-Timinnvintearationdlesmills.comselect * from playbooks where team_id = 711; # event 226147SELEd * FROM DLavbook catedonies WHERE Dlavbook 10 = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event' ;SELECT * SROM eom fields WHERE1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;Cascade50 lalU AskJiminnyReportActivityServiceTest~100% L28• Fri 22 May 13:46:43+0 ..Start a New Conversation SeLCascade Code *or.Kick off a new project. Make changesacross vour entire codebaseHubspot Activity lype Discrepancy Investigation© Al Activity Type Flow ExplanationAsk anything (J+L)<> Code SWE-1.636m22h1 dittoronsoleurrent version"Level' →> env('LOG_LEVEL', "'info'),'path' => storage_path('logs/Laravel.Log'),'custom channel' »> ["driver' => 'single','path' => storage_path('logs/custom.1og'),'Level' => env('LOG_LEVEL', 'info'),W Windsurf Teams 594:60 UTF-84 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70884
|
2545
|
36
|
2026-05-22T10:46:40.557826+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446800557_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhostormFV faVsco.js~VIewg9 master kNavigateCodeE PhostormFV faVsco.js~VIewg9 master kNavigateCodeE infection.json.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeM+licenses.mdM MakefileO package-lock.jsonE phpstan.neon.distE phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdộ sonar-project.propertiesE test.py‹› Untided Diagram.XmiIS vetur.config.jsw+ WEBHOOKFILIERING_IMPLEMENTATION.md› ih External Librariesv E°Scratches and Consoles~ D Database ConsolesVLEU& console lEUl& DEALRISKS EUIADITEUIAEU TEUv A jiminny@localhostconsole lliminnv@llocalhostiDI jiminny@localhost]4 HS local fiminnv@localhost)#SF [jiminny@localhost]Azoho dev fiminnv@localhostiV A PRODA console [PROD]A console_1 [PROD]A DI [PROD]123 (Cg>Local ChangesShelfiConsoleLoa]E .env.local app© JiminnyDebugCommand.php app/Console/CommandsUnversioned Files 9 files=.env.nikilocal appE .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdTo raw_sql_query.sql appc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootM+ WEBHOOK_FILTERING_IMPLEMENTATION.md appworkspace associated with branch master nas been restored // kollback // Contigure... (a minute ago)) ConvertLeadActivities.phposynerorlannatonpPlaybackController.phppnp api.phgC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho© AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp apL_v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends Controller30 €pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepository,privace readonly Aucomareckeporcsservice saucomareakeportsservice.BI4NLAYprivate readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService SplanhatService,) 4.3PosT/aoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request $request): JsonResponsef...** othrows AoplicationExceotionG-Taoi/vlautomated-renortspublic function List(Request Srequest): JsonResponsef...7DELETE /api/v1/automated-reports/(uuid)public function delete(Request Srequest, string $uuid): JsonResponsef...}E custom.logA console [STAGING]E laravel.log4 SF [jiminny@localhost]A HS_Jocal [jiminny@localhost]A console [PROD] x& console [EU]CascadeInvestigate Foreign Ke580584587589590593594595596T597-598599600Tx: AutovCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE' END) AS USer_1fajiminny044 A1 A41 X66 A Vsa.*,t.owner_id FROM social_accounts saJOIN users u on U.id = sa.sociable_idJOIN teams t 1..n<-›1: on t.id = u.team_idWHERE u.team_id = 581 and sa.provider = 'salesforce':SELECT * FROM automated_report_results order by id desc;select * from features;select * from team_features where feature_id = 40;select * from teams where id = 556;select * trom automated reports;where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMautomated report resulrs Wiake 10 = 19191select * fromautomated report results WHERE report id = 54:select * from ooportunities where 1d = 75945491SELE * FROM teamsWHERE name LIKE "%lesh': # 711, 692. 16067-Timinnvintearationdlesmills.comselect * from playbooks where team_id = 711; # event 226147SELEd * FROM DLavbook catedonies WHERE Dlavbook 10 = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event' ;SELECT * SROM eom fields WHERE1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;Cascade50 lalU AskJiminnyReportActivityServiceTest~100% L28• Fri 22 May 13:46:40+0 ..Start a New Conversation SLCascade Code *or.Kick off a new project. Make changesacross vour entire codebaseHubspot Activity lype Discrepancy Investigation© Al Activity Type Flow ExplanationAsk anything (J+L)<> Code SWE-1.636m22h1 dittoronsolTJ& < → Side-by-side viewer -olaeezaad confia/loaaina.phpDo not ignore'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('Logs/Laravel. Log'),eurrent version'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/Laravel. l0g'),'custom channel' »> ["driver' => 'single','path' => storage_path('logs/custom.1og'),'Level' => env('LOG_LEVEL', 'info'),W Windsurf Teams 594:60 UTF-84 spaces...
|
NULL
|
5559377753385539278
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhostormFV faVsco.js~VIewg9 master kNavigateCodeE PhostormFV faVsco.js~VIewg9 master kNavigateCodeE infection.json.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeM+licenses.mdM MakefileO package-lock.jsonE phpstan.neon.distE phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdộ sonar-project.propertiesE test.py‹› Untided Diagram.XmiIS vetur.config.jsw+ WEBHOOKFILIERING_IMPLEMENTATION.md› ih External Librariesv E°Scratches and Consoles~ D Database ConsolesVLEU& console lEUl& DEALRISKS EUIADITEUIAEU TEUv A jiminny@localhostconsole lliminnv@llocalhostiDI jiminny@localhost]4 HS local fiminnv@localhost)#SF [jiminny@localhost]Azoho dev fiminnv@localhostiV A PRODA console [PROD]A console_1 [PROD]A DI [PROD]123 (Cg>Local ChangesShelfiConsoleLoa]E .env.local app© JiminnyDebugCommand.php app/Console/CommandsUnversioned Files 9 files=.env.nikilocal appE .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdTo raw_sql_query.sql appc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootM+ WEBHOOK_FILTERING_IMPLEMENTATION.md appworkspace associated with branch master nas been restored // kollback // Contigure... (a minute ago)) ConvertLeadActivities.phposynerorlannatonpPlaybackController.phppnp api.phgC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho© AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp apL_v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends Controller30 €pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepository,privace readonly Aucomareckeporcsservice saucomareakeportsservice.BI4NLAYprivate readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService SplanhatService,) 4.3PosT/aoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request $request): JsonResponsef...** othrows AoplicationExceotionG-Taoi/vlautomated-renortspublic function List(Request Srequest): JsonResponsef...7DELETE /api/v1/automated-reports/(uuid)public function delete(Request Srequest, string $uuid): JsonResponsef...}E custom.logA console [STAGING]E laravel.log4 SF [jiminny@localhost]A HS_Jocal [jiminny@localhost]A console [PROD] x& console [EU]CascadeInvestigate Foreign Ke580584587589590593594595596T597-598599600Tx: AutovCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE' END) AS USer_1fajiminny044 A1 A41 X66 A Vsa.*,t.owner_id FROM social_accounts saJOIN users u on U.id = sa.sociable_idJOIN teams t 1..n<-›1: on t.id = u.team_idWHERE u.team_id = 581 and sa.provider = 'salesforce':SELECT * FROM automated_report_results order by id desc;select * from features;select * from team_features where feature_id = 40;select * from teams where id = 556;select * trom automated reports;where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMautomated report resulrs Wiake 10 = 19191select * fromautomated report results WHERE report id = 54:select * from ooportunities where 1d = 75945491SELE * FROM teamsWHERE name LIKE "%lesh': # 711, 692. 16067-Timinnvintearationdlesmills.comselect * from playbooks where team_id = 711; # event 226147SELEd * FROM DLavbook catedonies WHERE Dlavbook 10 = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event' ;SELECT * SROM eom fields WHERE1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;Cascade50 lalU AskJiminnyReportActivityServiceTest~100% L28• Fri 22 May 13:46:40+0 ..Start a New Conversation SLCascade Code *or.Kick off a new project. Make changesacross vour entire codebaseHubspot Activity lype Discrepancy Investigation© Al Activity Type Flow ExplanationAsk anything (J+L)<> Code SWE-1.636m22h1 dittoronsolTJ& < → Side-by-side viewer -olaeezaad confia/loaaina.phpDo not ignore'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('Logs/Laravel. Log'),eurrent version'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('logs/Laravel. l0g'),'custom channel' »> ["driver' => 'single','path' => storage_path('logs/custom.1og'),'Level' => env('LOG_LEVEL', 'info'),W Windsurf Teams 594:60 UTF-84 spaces...
|
70883
|
NULL
|
NULL
|
NULL
|
|
70883
|
2545
|
35
|
2026-05-22T10:46:37.550077+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446797550_m2.jpg...
|
Firefox
|
[JY-20912] Fallback mechanism for users with activ [JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20912
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8852295099697676704
|
7230047640003280981
|
visual_change
|
hybrid
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
FirefoxEditVIewMistorbookmarksProtllesPlatform Sprint 4 Q2 - Platform Te[JY-20912] Fallback mechanisr XService-Desk - Queues - Platform• JY-20676 delete AJ reports relatef. JY-20963 fix deleted object impor© Pipelines - jiminny/apps)SevenShores|Hubspot\Exceptions8 JiminnyJY-20879) Enable users to use thiSentry error)JY-20963 fix deleted object impor# (JY-20813] Twilio number is not rel+ New TabToolsWindowmelpny.atlassian.net/browse/JY-20912O JIMINNY@ For you(• Recent|# Starred8$ AppsQ Spaces+ ...2 Jiminny (New) + ...IWD Platform TeamIID Capture TeamWD Enterprise Stability I…..W Processing TeamIN SE KanbanC Service-Desk= More spaces— FiltersCB Dashboards@ Operations2 Confluence: Teams"= Customise sidebarIQMOAX 00%E 8• Fri22May 13:46:37E ASK ROVO A ® Lô 0Q SearchSpaces / Jiminny (New) / & sY-12250 / [ JY-20912Fallback mechanism for users with active SF tokens for CRM Matching~ DescriptionWe identified an issue for Tourlane where Twillo calls appear as "Unknown Customer" when the Salesforce admin connection token has expired. In this case, CRMobject matching fails because the system relies only on the admin's Salesforce token.We should implement the same fallback logic currently used for dialers - if the Salesforce admin token is expired or inactive, the system should search for another activeSalesforce token from any user within the same organisation and use it for CRM matching.Acceptance Criteria:• If the Salesforce admin token has expired, the system searches for another active Salesforce token within the organisation.• CRM object matching continues successfully using the fallback token.• Calls should not appear as "Unknown Customer" when another valid Salesforce connection exists in the organisation.• update this in all places - in all CRM related functionalitiesSubtasksAdd subtaskLinked work itemsAdd linked work item~ ActivityHistory Work logAdd a comment…..Suggest a reply... Who is working on this...? Status update...Pro tip: press (M) to commentnvniay vonunit4 days ago (edited)RF: 1 dav+ CreateBacklog~ * Improve StoryDetailsAssigneeReporterDevelopmentComponentsSub-ProductLabelsStory point estimateStory PointsOrganisationsPriorityFix versionsSprintDaysNeed QA& UnassignedAssign to me@ Adelina Petrova[ Open with VS Code33 Create branch/ Create commitPlatformAdd optionsNoneNoneNoneNone= MediumNonePlatform Sprint 5 Q2ParentCanny LinksNO* Jy-12250 Reduce Churn)Open Canny Links> More fields Original estimate, Time tracking> Automation 4 Rule executions> featureOS Open featureos022....
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70882
|
2545
|
34
|
2026-05-22T10:46:35.740561+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446795740_m2.jpg...
|
Firefox
|
[JY-20912] Fallback mechanism for users with activ [JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20912
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.09177973,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.106715426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"bounds":{"left":0.0028257978,"top":0.35155627,"width":0.024601065,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37071028,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38188347,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":4,"bounds":{"left":0.0,"top":0.40343177,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.41460496,"width":0.14793883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.43774942,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"bounds":{"left":0.090259306,"top":0.07861133,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"bounds":{"left":0.08361037,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"bounds":{"left":0.0887633,"top":0.06344773,"width":0.039727394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"bounds":{"left":0.095578454,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"bounds":{"left":0.10073138,"top":0.06344773,"width":0.044215426,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"bounds":{"left":0.10887633,"top":0.057861134,"width":0.029421542,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"bounds":{"left":0.40475398,"top":0.06264964,"width":0.24268617,"height":0.015961692},"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"bounds":{"left":0.65575135,"top":0.057861134,"width":0.030086435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"bounds":{"left":0.66705453,"top":0.06384677,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"bounds":{"left":0.91223407,"top":0.057861134,"width":0.035904255,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"bounds":{"left":0.92353725,"top":0.06384677,"width":0.020611702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"bounds":{"left":0.9494681,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"bounds":{"left":0.954621,"top":0.06344773,"width":0.027759308,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"bounds":{"left":0.96143615,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"bounds":{"left":0.9665891,"top":0.06344773,"width":0.010139627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.9734042,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.97855717,"top":0.06344773,"width":0.017952127,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.98537236,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"bounds":{"left":0.99052525,"top":0.06344773,"width":0.009474754,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"bounds":{"left":0.08361037,"top":0.09976058,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"bounds":{"left":0.09424867,"top":0.10574621,"width":0.01662234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"bounds":{"left":0.08361037,"top":0.12529927,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"bounds":{"left":0.09424867,"top":0.13128492,"width":0.015458777,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"bounds":{"left":0.08361037,"top":0.15083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"bounds":{"left":0.09424867,"top":0.15682362,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"bounds":{"left":0.08361037,"top":0.1763767,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"bounds":{"left":0.09424867,"top":0.18236233,"width":0.011635638,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"bounds":{"left":0.15309176,"top":0.17956904,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"bounds":{"left":0.08361037,"top":0.2019154,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"bounds":{"left":0.09424867,"top":0.20790103,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"bounds":{"left":0.13646941,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"bounds":{"left":0.14577793,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"bounds":{"left":0.08959442,"top":0.23423783,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"bounds":{"left":0.08759973,"top":0.2529928,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"bounds":{"left":0.09823803,"top":0.25897846,"width":0.032081116,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"bounds":{"left":0.08892952,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXMenuButton","text":"Create board","depth":18,"bounds":{"left":0.13646941,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"bounds":{"left":0.14577793,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform Team","depth":19,"bounds":{"left":0.09158909,"top":0.27853152,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team","depth":22,"bounds":{"left":0.1022274,"top":0.28451717,"width":0.032247342,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.28172386,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Capture Team","depth":19,"bounds":{"left":0.09158909,"top":0.30407023,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Capture Team","depth":22,"bounds":{"left":0.1022274,"top":0.31005585,"width":0.03125,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.30726257,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Enterprise Stability Issues 🤕","depth":19,"bounds":{"left":0.09158909,"top":0.32960895,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enterprise Stability Issues 🤕","depth":22,"bounds":{"left":0.1022274,"top":0.33559456,"width":0.050531916,"height":0.030726258},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.33280128,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
6786673803561298851
|
1375682185621196933
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions...
|
70880
|
NULL
|
NULL
|
NULL
|
|
70881
|
2544
|
30
|
2026-05-22T10:46:35.636062+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446795636_m1.jpg...
|
Firefox
|
[JY-20912] Fallback mechanism for users with activ [JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20912
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
4873342848382306278
|
-9005110583191080315
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70880
|
2545
|
33
|
2026-05-22T10:46:34.501148+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446794501_m2.jpg...
|
Firefox
|
[JY-20912] Fallback mechanism for users with activ [JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20912
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New) Jiminny (New)
Jiminny (New)
/
Epic - Change parent
JY-12250...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.09177973,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.106715426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"bounds":{"left":0.0028257978,"top":0.35155627,"width":0.024601065,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37071028,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38188347,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":4,"bounds":{"left":0.0,"top":0.40343177,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.41460496,"width":0.14793883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.43774942,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"bounds":{"left":0.090259306,"top":0.07861133,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"bounds":{"left":0.08361037,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"bounds":{"left":0.0887633,"top":0.06344773,"width":0.039727394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"bounds":{"left":0.095578454,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"bounds":{"left":0.10073138,"top":0.06344773,"width":0.044215426,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"bounds":{"left":0.10887633,"top":0.057861134,"width":0.029421542,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"bounds":{"left":0.40475398,"top":0.06264964,"width":0.24268617,"height":0.015961692},"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"bounds":{"left":0.65575135,"top":0.057861134,"width":0.030086435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"bounds":{"left":0.66705453,"top":0.06384677,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"bounds":{"left":0.91223407,"top":0.057861134,"width":0.035904255,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"bounds":{"left":0.92353725,"top":0.06384677,"width":0.020611702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Notifications","depth":12,"bounds":{"left":0.9494681,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notifications","depth":14,"bounds":{"left":0.954621,"top":0.06344773,"width":0.027759308,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"bounds":{"left":0.96143615,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"bounds":{"left":0.9665891,"top":0.06344773,"width":0.010139627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.9734042,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.97855717,"top":0.06344773,"width":0.017952127,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.98537236,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"bounds":{"left":0.99052525,"top":0.06344773,"width":0.009474754,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"bounds":{"left":0.08361037,"top":0.09976058,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"bounds":{"left":0.09424867,"top":0.10574621,"width":0.01662234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"bounds":{"left":0.08361037,"top":0.12529927,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"bounds":{"left":0.09424867,"top":0.13128492,"width":0.015458777,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"bounds":{"left":0.08361037,"top":0.15083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"bounds":{"left":0.09424867,"top":0.15682362,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"bounds":{"left":0.08361037,"top":0.1763767,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"bounds":{"left":0.09424867,"top":0.18236233,"width":0.011635638,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"bounds":{"left":0.15309176,"top":0.17956904,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"bounds":{"left":0.08361037,"top":0.2019154,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"bounds":{"left":0.09424867,"top":0.20790103,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"bounds":{"left":0.13646941,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"bounds":{"left":0.14577793,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"bounds":{"left":0.08959442,"top":0.23423783,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"bounds":{"left":0.08759973,"top":0.2529928,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"bounds":{"left":0.09823803,"top":0.25897846,"width":0.032081116,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"bounds":{"left":0.08892952,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXMenuButton","text":"Create board","depth":18,"bounds":{"left":0.13646941,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"bounds":{"left":0.14577793,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Platform Team","depth":19,"bounds":{"left":0.09158909,"top":0.27853152,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Team","depth":22,"bounds":{"left":0.1022274,"top":0.28451717,"width":0.032247342,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.28172386,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Capture Team","depth":19,"bounds":{"left":0.09158909,"top":0.30407023,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Capture Team","depth":22,"bounds":{"left":0.1022274,"top":0.31005585,"width":0.03125,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.30726257,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Enterprise Stability Issues 🤕","depth":19,"bounds":{"left":0.09158909,"top":0.32960895,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Enterprise Stability Issues 🤕","depth":22,"bounds":{"left":0.1022274,"top":0.33559456,"width":0.050531916,"height":0.030726258},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.33280128,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Processing Team","depth":19,"bounds":{"left":0.09158909,"top":0.35514766,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Processing Team","depth":22,"bounds":{"left":0.1022274,"top":0.36113328,"width":0.038231384,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.35834,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"SE Kanban","depth":19,"bounds":{"left":0.09158909,"top":0.38068634,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SE Kanban","depth":22,"bounds":{"left":0.1022274,"top":0.386672,"width":0.024102394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Board actions","depth":20,"bounds":{"left":0.15309176,"top":0.38387868,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Board actions","depth":22,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service-Desk","depth":17,"bounds":{"left":0.08759973,"top":0.40622506,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Service-Desk","depth":20,"bounds":{"left":0.09823803,"top":0.4122107,"width":0.03025266,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Service-Desk","depth":18,"bounds":{"left":0.15442154,"top":0.4094174,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Service-Desk","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More spaces","depth":17,"bounds":{"left":0.08759973,"top":0.43176377,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More spaces","depth":20,"bounds":{"left":0.09823803,"top":0.43774942,"width":0.028756648,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Filters","depth":12,"bounds":{"left":0.08361037,"top":0.45730248,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Filters","depth":15,"bounds":{"left":0.09424867,"top":0.4632881,"width":0.013796543,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Filters","depth":13,"bounds":{"left":0.15309176,"top":0.46049482,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Filters","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Dashboards","depth":12,"bounds":{"left":0.08361037,"top":0.4828412,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Dashboards","depth":15,"bounds":{"left":0.09424867,"top":0.4888268,"width":0.026761968,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create dashboard","depth":13,"bounds":{"left":0.15508644,"top":0.48603353,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create dashboard","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Dashboards","depth":13,"bounds":{"left":0.16240026,"top":0.48603353,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Dashboards","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Operations","depth":12,"bounds":{"left":0.08361037,"top":0.5083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Operations","depth":15,"bounds":{"left":0.09424867,"top":0.5143655,"width":0.02443484,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Operations","depth":13,"bounds":{"left":0.15309176,"top":0.51157224,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Operations","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Confluence , (opens new window)","depth":13,"bounds":{"left":0.08361037,"top":0.5434956,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Confluence","depth":17,"bounds":{"left":0.09424867,"top":0.5494813,"width":0.025764627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"bounds":{"left":0.08361037,"top":0.55706304,"width":0.04837101,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Teams , (opens new window)","depth":13,"bounds":{"left":0.08361037,"top":0.56903434,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Teams","depth":17,"bounds":{"left":0.09424867,"top":0.57501996,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":", (opens new window)","depth":15,"bounds":{"left":0.08361037,"top":0.5826017,"width":0.04837101,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"open menu","depth":14,"bounds":{"left":0.14378324,"top":0.57222664,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"open menu","depth":16,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Customise sidebar","depth":12,"bounds":{"left":0.08361037,"top":0.60415006,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Customise sidebar","depth":15,"bounds":{"left":0.09424867,"top":0.6101357,"width":0.04155585,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Resize side navigation panel","depth":13,"bounds":{"left":0.2109375,"top":0.0981644,"width":0.062333778,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Spaces","depth":15,"bounds":{"left":0.27260637,"top":0.10933759,"width":0.013962766,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Spaces","depth":17,"bounds":{"left":0.27260637,"top":0.11292897,"width":0.013962766,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"bounds":{"left":0.2883976,"top":0.11173184,"width":0.0016622341,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New) Jiminny (New)","depth":15,"bounds":{"left":0.29388297,"top":0.10933759,"width":0.034408245,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":17,"bounds":{"left":0.3011968,"top":0.11292897,"width":0.027094414,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":15,"bounds":{"left":0.33011967,"top":0.11173184,"width":0.0016622341,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Epic - Change parent","depth":15,"bounds":{"left":0.3336104,"top":0.10933759,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"JY-12250","depth":15,"bounds":{"left":0.3415891,"top":0.10933759,"width":0.017952127,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
8300158663751006242
|
1375119233498861764
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Sidebar
Sidebar
Top Bar
Top Bar
Main Content
Main Content
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Notifications
Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Platform Team
Platform Team
Board actions
Board actions
Capture Team
Capture Team
Board actions
Board actions
Enterprise Stability Issues 🤕
Enterprise Stability Issues 🤕
Board actions
Board actions
Processing Team
Processing Team
Board actions
Board actions
SE Kanban
SE Kanban
Board actions
Board actions
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
More spaces
More spaces
Filters
Filters
More actions for Filters
More actions for Filters
Dashboards
Dashboards
Create dashboard
Create dashboard
More actions for Dashboards
More actions for Dashboards
Operations
Operations
More actions for Operations
More actions for Operations
Confluence , (opens new window)
Confluence
, (opens new window)
Teams , (opens new window)
Teams
, (opens new window)
open menu
open menu
Customise sidebar
Customise sidebar
Resize side navigation panel
Spaces
Spaces
/
Jiminny (New) Jiminny (New)
Jiminny (New)
/
Epic - Change parent
JY-12250...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70879
|
2545
|
32
|
2026-05-22T10:46:32.981472+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446792981_m2.jpg...
|
Firefox
|
[JY-20912] Fallback mechanism for users with activ [JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20912
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Queue
Queue
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Over 9 Notifications
Over 9 Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
Summary
Summary
More actions for summary
More actions for summary
Queues
Queues
Create
Create
More for queues
More for queues
Team Priority
Team Priority
All open tickets
All open tickets
Star All open tickets
15
Unassigned tickets
Unassigned tickets...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.09177973,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.1245012,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.106715426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"bounds":{"left":0.0028257978,"top":0.35155627,"width":0.024601065,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37071028,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38188347,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":4,"bounds":{"left":0.0,"top":0.40343177,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.41460496,"width":0.14793883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.43774942,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to:","depth":9,"bounds":{"left":0.090259306,"top":0.07861133,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Top Bar","depth":10,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Top Bar","depth":11,"bounds":{"left":0.090259306,"top":0.097765364,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sidebar","depth":10,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sidebar","depth":11,"bounds":{"left":0.090259306,"top":0.11691939,"width":0.016954787,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Main Content","depth":10,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Main Content","depth":11,"bounds":{"left":0.090259306,"top":0.13607343,"width":0.029421542,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Queue","depth":10,"bounds":{"left":0.090259306,"top":0.15522745,"width":0.014461436,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Queue","depth":11,"bounds":{"left":0.090259306,"top":0.15522745,"width":0.014461436,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse sidebar [","depth":9,"bounds":{"left":0.08361037,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Collapse sidebar [","depth":11,"bounds":{"left":0.0887633,"top":0.06344773,"width":0.039727394,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Switch sites or apps","depth":10,"bounds":{"left":0.095578454,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Switch sites or apps","depth":12,"bounds":{"left":0.10073138,"top":0.06344773,"width":0.044215426,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Go to your Jira homepage","depth":9,"bounds":{"left":0.10887633,"top":0.057861134,"width":0.029421542,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXComboBox","text":"Search, press enter to navigate to advanced search with your text query","depth":11,"bounds":{"left":0.40475398,"top":0.06264964,"width":0.24268617,"height":0.015961692},"on_screen":true,"help_text":"","placeholder":"Search","role_description":"combo box","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Create","depth":10,"bounds":{"left":0.65575135,"top":0.057861134,"width":0.030086435,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create","depth":12,"bounds":{"left":0.66705453,"top":0.06384677,"width":0.014793883,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Rovo Ask Rovo","depth":12,"bounds":{"left":0.91223407,"top":0.057861134,"width":0.035904255,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Ask Rovo","depth":14,"bounds":{"left":0.92353725,"top":0.06384677,"width":0.020611702,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Over 9 Notifications","depth":12,"bounds":{"left":0.9494681,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Over 9 Notifications","depth":14,"bounds":{"left":0.954621,"top":0.06344773,"width":0.043882977,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Help","depth":12,"bounds":{"left":0.96143615,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Help","depth":14,"bounds":{"left":0.9665891,"top":0.06344773,"width":0.010139627,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Settings","depth":12,"bounds":{"left":0.9734042,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.97855717,"top":0.06344773,"width":0.017952127,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"lukas.kovalik@jiminny.com","depth":12,"bounds":{"left":0.98537236,"top":0.057861134,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"lukas.kovalik@jiminny.com","depth":14,"bounds":{"left":0.99052525,"top":0.06344773,"width":0.009474754,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"For you","depth":12,"bounds":{"left":0.08361037,"top":0.09976058,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"For you","depth":15,"bounds":{"left":0.09424867,"top":0.10574621,"width":0.01662234,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Recent","depth":12,"bounds":{"left":0.08361037,"top":0.12529927,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Recent","depth":15,"bounds":{"left":0.09424867,"top":0.13128492,"width":0.015458777,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Starred","depth":12,"bounds":{"left":0.08361037,"top":0.15083799,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Starred","depth":15,"bounds":{"left":0.09424867,"top":0.15682362,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Apps","depth":12,"bounds":{"left":0.08361037,"top":0.1763767,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Apps","depth":15,"bounds":{"left":0.09424867,"top":0.18236233,"width":0.011635638,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Apps","depth":13,"bounds":{"left":0.15309176,"top":0.17956904,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Apps","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Spaces","depth":12,"bounds":{"left":0.08361037,"top":0.2019154,"width":0.071476065,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Spaces","depth":15,"bounds":{"left":0.09424867,"top":0.20790103,"width":0.016456118,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create space","depth":13,"bounds":{"left":0.13646941,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Create space","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for spaces","depth":13,"bounds":{"left":0.14577793,"top":0.20510775,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for spaces","depth":15,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Recent","depth":16,"bounds":{"left":0.08959442,"top":0.23423783,"width":0.013464096,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Jiminny (New)","depth":17,"bounds":{"left":0.08759973,"top":0.2529928,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny (New)","depth":20,"bounds":{"left":0.09823803,"top":0.25897846,"width":0.032081116,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Jiminny (New)","depth":18,"bounds":{"left":0.08892952,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create board","depth":18,"bounds":{"left":0.15508644,"top":0.25618514,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create board","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Jiminny (New)","depth":18,"bounds":{"left":0.16240026,"top":0.25618514,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Jiminny (New)","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Service-Desk","depth":17,"bounds":{"left":0.08759973,"top":0.27853152,"width":0.0674867,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Service-Desk","depth":20,"bounds":{"left":0.09823803,"top":0.28451717,"width":0.03025266,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for Service-Desk","depth":18,"bounds":{"left":0.14577793,"top":0.28172386,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for Service-Desk","depth":20,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Summary","depth":21,"bounds":{"left":0.09158909,"top":0.30407023,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Summary","depth":24,"bounds":{"left":0.1022274,"top":0.31005585,"width":0.021276595,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More actions for summary","depth":22,"bounds":{"left":0.15309176,"top":0.30726257,"width":0.0039893617,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More actions for summary","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Queues","depth":21,"bounds":{"left":0.09158909,"top":0.32960895,"width":0.06349734,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Queues","depth":24,"bounds":{"left":0.1022274,"top":0.33559456,"width":0.017121011,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Create","depth":22,"bounds":{"left":0.13646941,"top":0.33280128,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Create","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"More for queues","depth":22,"bounds":{"left":0.14577793,"top":0.33280128,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"More for queues","depth":24,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Team Priority","depth":23,"bounds":{"left":0.095578454,"top":0.35514766,"width":0.059507977,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"Team Priority","depth":26,"bounds":{"left":0.10621676,"top":0.36113328,"width":0.029587766,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"All open tickets","depth":25,"bounds":{"left":0.099567816,"top":0.38068634,"width":0.055518616,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"All open tickets","depth":28,"bounds":{"left":0.11020612,"top":0.386672,"width":0.034075797,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXCheckBox","text":"Star All open tickets","depth":26,"bounds":{"left":0.14577793,"top":0.38387868,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"15","depth":28,"bounds":{"left":0.14777261,"top":0.38826814,"width":0.0039893617,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unassigned tickets","depth":25,"bounds":{"left":0.099567816,"top":0.40622506,"width":0.055518616,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unassigned tickets","depth":28,"bounds":{"left":0.11020612,"top":0.4122107,"width":0.03307846,"height":0.029928172},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-4441596482904575928
|
-8964582595831901051
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
[JY-20813] Twilio number is not released when Voice role is removed from user - Jira
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to:
Top Bar
Top Bar
Sidebar
Sidebar
Main Content
Main Content
Queue
Queue
Collapse sidebar [
Collapse sidebar [
Switch sites or apps
Switch sites or apps
Go to your Jira homepage
Search, press enter to navigate to advanced search with your text query
Create
Create
Rovo Ask Rovo
Ask Rovo
Over 9 Notifications
Over 9 Notifications
Help
Help
Settings
Settings
[EMAIL]
[EMAIL]
For you
For you
Recent
Recent
Starred
Starred
Apps
Apps
More actions for Apps
More actions for Apps
Spaces
Spaces
Create space
Create space
More actions for spaces
More actions for spaces
Recent
Jiminny (New)
Jiminny (New)
Jiminny (New)
Create board
Create board
More actions for Jiminny (New)
More actions for Jiminny (New)
Service-Desk
Service-Desk
More actions for Service-Desk
More actions for Service-Desk
Summary
Summary
More actions for summary
More actions for summary
Queues
Queues
Create
Create
More for queues
More for queues
Team Priority
Team Priority
All open tickets
All open tickets
Star All open tickets
15
Unassigned tickets
Unassigned tickets...
|
70877
|
NULL
|
NULL
|
NULL
|
|
70878
|
2544
|
29
|
2026-05-22T10:46:32.854103+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446792854_m1.jpg...
|
Firefox
|
[JY-20912] Fallback mechanism for users with activ [JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira — Work...
|
1
|
jiminny.atlassian.net/browse/JY-20912
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-414975891407645628
|
-2029324997941497723
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Close tab
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny...
|
70876
|
NULL
|
NULL
|
NULL
|
|
70876
|
2544
|
28
|
2026-05-22T10:46:31.949659+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446791949_m1.jpg...
|
Firefox
|
Service-Desk - Queues - Platform team - Service sp Service-Desk - Queues - Platform team - Service space - Jira — Work...
|
1
|
jiminny.atlassian.net/jira/servicedesk/projects/SR jiminny.atlassian.net/jira/servicedesk/projects/SRD/queues/custom/37...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-8798911099898858392
|
-4443254535130943355
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Close tab
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70877
|
2545
|
31
|
2026-05-22T10:46:31.332390+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446791332_m2.jpg...
|
Firefox
|
JY-20963 fix deleted object import by LakyLak · Pu JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app — Work...
|
1
|
jiminny.atlassian.net/jira/servicedesk/projects/SR jiminny.atlassian.net/jira/servicedesk/projects/SRD/queues/custom/37...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app...
|
[{"role":"AXStaticText","text& [{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.08361037,"top":0.15722266,"width":0.08427527,"height":0.021548284},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"github.com","depth":4,"bounds":{"left":0.08361037,"top":0.17877094,"width":0.019614361,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.15722266,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.18994413,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.106715426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"bounds":{"left":0.0028257978,"top":0.35155627,"width":0.024601065,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37071028,"width":0.07962101,"height":0.032721467},"on_screen":true,"subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
2023551270782151166
|
-3294818935458729593
|
visual_change
|
accessibility
|
NULL
|
JY-20676 delete AJ reports related objects by Laky JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
github.com
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
Close tab
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny
[JY-20879] Enable users to use their new activity types - Jira
[JY-20879] Enable users to use their new activity types - Jira
Sentry error
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70875
|
2545
|
30
|
2026-05-22T10:46:28.092326+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446788092_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4235983745889776938
|
-8204421443435123770
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
master, menu
Start Listen 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
PhostormFV faVsco.js vVIewg9 master kNavigateCodeE infection.json.distM+INSTALLmdM.INTERNALWEBHOOK SETUP.md=liminny storadeM+licenses.mdM MakefileO package-lock.jsonE phpstan.neon.distE phpstan-baseline.neon< phpunit.xmlTa raw_sqLquery.sqlM+ README.mdộ sonar-project.propertiesE test.py‹› Untided Diagram.XmiIS vetur.config.jsw+ WEBHOOKFILIERING_IMPLEMENTATION.md› ih External Librariesv E°Scratches and Consoles~ D Database ConsolesVLEU& console lEUl& DEALRISKS EUIADITEUIAEU TEUv A jiminny@localhostconsole lliminnv@llocalhostiDI jiminny@localhost]4 HS local fiminnv@localhost)#SF [jiminny@localhost]Azoho dev fiminnv@localhostiV A PRODA console [PROD]A console_1 [PROD]A DI [PROD]123 (Сg >Local ChangesShelfiConsoleLoa]E .env.local app© JiminnyDebugCommand.php app/Console/CommandsUnversioned Files 9 files=.env.nikilocal appE .env.other app© CanAccessAiReportsTest.php tests/Unit/Policies© CreateMockAskJiminnyReportResultCommand.php app/Console/Commands/R.ki tavicon.ico public= ids.txt apdTo raw_sql_query.sql appc) SimulateWe.hookscommand.ohoaon/console/commands/crm/=uosootM+ WEBHOOK_FILTERING_IMPLEMENTATION.md appworkspace associated with branch master nas been restored // kollback // Contigure... (a minute ago)) ConvertLeadActivities.phposynerorlannatonpPlaybackController.phppnp api.phgC) CreateSmsSentEvent.php(C) PlanhatActivityListener.pho© AskAnythingPromptService.php© AutomatedReportsRepository.php(C) AutomatedReportsCommand.phpphp apL_v2.phpC) RequestGenerateReportJob.pho(C) AutomatedReportResult.php© AutomatedReport.phpclass UserAutomatedReportsController extends Controller30 €pubulc tunccion -_conscruceprivate readonly AutomatedReportsRepository $automatedReportsRepository,privace readonly Aucomareckeporcsservice saucomareakeportsservice.BI4NLAYprivate readonly Apikesponseservice sapikesponseservice,private readonly kesponse sresponse.private readonly PlanhatService SplanhatService,) 4.3PosT/aoi/vllautomated-reoorts/interest usagepublic function trackInterest(Request $request): JsonResponsef...** othrows AoplicationExceotionG-Taoi/vlautomated-renortspublic function List(Request Srequest): JsonResponsef...7DELETE /api/v1/automated-reports/(uuid)public function delete(Request Srequest, string $uuid): JsonResponsef...}E custom.logA console [STAGING]E laravel.log4 SF [jiminny@localhost]A HS_Jocal [jiminny@localhost]A console [PROD] x& console [EU]CascadeInvestigate Foreign Ke580584587589590593594595596T597-598599600Tx: AutovCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE' END) AS USer_1fajiminny044 A1 A41 X66 A Vsa.*,t.owner_id FROM social_accounts saJOIN users u on U.id = sa.sociable_idJOIN teams t 1..n<-›1: on t.id = u.team_idWHERE u.team_id = 581 and sa.provider = 'salesforce':SELECT * FROM automated_report_results order by id desc;select * from features;select * from team_features where feature_id = 40;select * from teams where id = 556;select * trom automated reports;where id = 54; # 4fdd41f6-dcf0-30d0-b339-7345381b6044SELECT * FROM automated_report_results WHERE uuid_to_bin('822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMautomated report resulrs Wiake 10 = 19191select * fromautomated report results WHERE report id = 54:select * from ooportunities where 1d = 75945491SELE * FROM teamsWHERE name LIKE "%lesh': # 711, 692. 16067-Timinnvintearationdlesmills.comselect * from playbooks where team_id = 711; # event 226147SELEd * FROM DLavbook catedonies WHERE Dlavbook 10 = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event' ;SELECT * SROM eom fields WHERE1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;Cascade50 lal100% L28• Fri 22 May 13:46:27AskJiminnyReportActivityServiceTest v+0 ..Cascade Code *or.Kick off a new project. Make changesacross vour entire codebaseHubspot Activity lype Discrepancy Investigation© Al Activity Type Flow ExplanationAsk anything (J+L)<> Code SWE-1.636m22h1 dittoronsolTJ& < → Side-by-side viewer -olaeezaad confia/loaaina.phpDo not ignore'Level' => env('LOG_LEVEL', 'info'),'path' => storage_path('Logs/Laravel. Log'),eurrent version"Level' →> env('LOG_LEVEL', "'info'),'path' => storage_path('logs/Laravel. Log'),'custom channel' »> ["driver' => 'single','path' => storage_path('logs/custom.1og'),'Level' => env('LOG_LEVEL', 'info'),Workspace associated with branch 'master' hasbeen restoredRollbackConfiaureW Windsurf Teams 594:60 UTF-84 spaces...
|
70874
|
NULL
|
NULL
|
NULL
|
|
70873
|
2544
|
27
|
2026-05-22T10:46:24.030463+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446784030_m1.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out master
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'master' has been restored","depth":3,"on_screen":true,"value":"Workspace associated with branch 'master' 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,"on_screen":false,"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,"on_screen":true,"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,"on_screen":false,"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,"on_screen":true,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Configure…","depth":2,"on_screen":true,"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},"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Checked out master","depth":3,"on_screen":true,"value":"Checked out master","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,"on_screen":false,"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,"on_screen":true,"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,"on_screen":true,"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,"on_screen":false,"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,"on_screen":true,"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,"on_screen":true,"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,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"AskJiminnyReportActivityServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'AskJiminnyReportActivityServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4138074332870044712
|
-7191967015203512378
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out master
text/html
text/html
text/html
text/html
Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'...
|
70872
|
NULL
|
NULL
|
NULL
|
|
70874
|
2545
|
29
|
2026-05-22T10:46:23.917919+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446783917_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out master
text/html...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'master' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.915403,"width":0.11037234,"height":0.027134877},"on_screen":true,"value":"Workspace associated with branch 'master' 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,"on_screen":false,"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.915403,"width":0.0987367,"height":0.027134877},"on_screen":true,"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,"on_screen":false,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Checked out master","depth":3,"bounds":{"left":0.016954787,"top":0.8595371,"width":0.04488032,"height":0.016759777},"on_screen":true,"value":"Checked out master","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,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5780303577183843303
|
-7776854432213182906
|
click
|
accessibility
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out master
text/html...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70872
|
2544
|
26
|
2026-05-22T10:46:18.360417+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446778360_m1.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'master' has been restored","depth":3,"on_screen":true,"value":"Workspace associated with branch 'master' 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,"on_screen":false,"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,"on_screen":true,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3986915542203260936
|
2022415962351194688
|
app_switch
|
hybrid
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
SlackFileEditViewGoHistoryWindowHelpAPP (DOCKERDEV (-zsh)O $82APP (-zsh)tests/Unit/Services/Activity/RingCentral/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/ServiceTest.phptests/Unit/Services/Activity/TwilioVideo/TwilioVideoMetadataHandlerTest.phptests/Unit/Services/Kiosk/AutomatedReports/AutomatedReportsServiceTest.phptests/Unit/Services/Mail/0ffice/EmailApiClientTest.phptests/Unit/Services/Mail/TextRelayServiceTest.phptests/Unit/Services/RecallAI/RecallAIServiceTest.php79 files changed, 4254 insertions(+), 2020 deletions(-)create mode 100644 .github/claude-reviewer/no-ticket-warning.txtcreate mode 100644 .github/claude-reviewer/scripts/fetch-jira-context.mjsdelete mode 100644 app/Component/ES/Processor/BatchStatusInterface.phpcreatemode 100644 app/Component/ES/Processor/RedisEntityExtractor.phpdelete modecreate mode 100644 app/Component/ES/QueuePriorityEnum.phpcreate mode 100644 front-end/src/components/DealInsights/Modals/__tests__/AskAnythingModacreate mode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Asdeletemode100644 front-end/src/components/shared/AskAnything/__tests__/__snapshots__/Ascreatemode 100644 tests/Unit/Component/ES/Processor/RedisEntityExtractorTest.phpdelete mode 100644 tests/Unit/Component/ES/Processor/Traits/WorkerIdTraitTest.phpcreate mode 100644 tests/Unit/Component/ES/QueuePriorityEnumTest.phpcreate mode 100644 tests/Unit/Notifications/Activities/SmsReceivedTest.phpcreate mode 100644 tests/Unit/Services/Mail/TextRelayServiceTest.phplukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (master) $ co -b JY-20963-fix-imporSwitched to a new branch 'JY-20963-fix-import-on-deleted-entity'lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20963-fix-import-on-deleted-entdocker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.phWhat's next:Try Docker Debug forseamless, persistent debugging tools in any container or image →Learn moreat [URL_WITH_CREDENTIALS] (JY-20963-fix-import-on-deleted-ent|docker exec -it docker_lamp_1./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.phPHP CS Fixer 3.87.1 Alexander by Fabien Potencier, Dariusz Ruminski and contributors.PHPruntime: 8.3.30Running analysis on 7 cores with 10 files perprocess.Parallel runner is an experimental feature and may be unstable,useitat yourownrisk.Loaded config default from".php-cs-fixer.dist.php"5691/5691100%Fixed 0 of 5691 files in 96.077 seconds, 67.00 MBmemory used•HomeDMsActivityFilesLaterMore+ED→Jiminny ...& jiminny-x-integrati...& platform-inner-teamE Channels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity _lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...0 Direct messagesGalya Dimitrova "o Nikolay YankovP. Aneliya Angelova. Stoyan Tanev2o James Graham> 0100% С8 • Fri 22 May 13:46:18Describe what you are looking for# releases8 22•MessagesC Files• Bookmarks+fetch PR data vi~ CitLluh API0f883d21 - RevToday~orking version1be25ca1 - Add github_token to Claude Codeaction for enhanced authenticationShow more( jiminny/app| Added by GitHubNewCircleCl APP 1:26 PMDeployment Successful!Project: appWhen:05/22/202610:26:05Tag:View JobGitHub APP 1:27 PM4 new commits pushed to master byTodorStamatovJiminnycbf9b72b - JY-20955: fix calendar eventduplication checkafe8dceg - Merge branch 'master' into JY-20955-multiple-calendars-events218a8602 - Merge branch 'master' into JY-20955-multiple-calendars-eventsbe560cbf - Merge pull request #12119 fromjiminny/JY-20955-multiple-calendars-eventsjiminny/app| Added by GitHubMessage #releases..•...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70871
|
2545
|
28
|
2026-05-22T10:46:18.198694+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446778198_m2.jpg...
|
PhpStorm
|
faVsco.js – console [PROD]
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out master
text/html
text/html
text/html
text/html...
|
[{"role":"AXTextField","text [{"role":"AXTextField","text":"Workspace associated with branch 'master' has been restored","depth":3,"bounds":{"left":0.8753325,"top":0.915403,"width":0.11037234,"height":0.027134877},"on_screen":true,"value":"Workspace associated with branch 'master' 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,"on_screen":false,"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.915403,"width":0.0987367,"height":0.027134877},"on_screen":true,"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,"on_screen":false,"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},"on_screen":true,"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},"on_screen":true,"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},"on_screen":false,"role_description":"link","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextField","text":"Checked out master","depth":3,"bounds":{"left":0.016954787,"top":0.8595371,"width":0.04488032,"height":0.016759777},"on_screen":true,"value":"Checked out master","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,"on_screen":false,"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},"on_screen":true,"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.01761968,"height":0.013567438},"on_screen":true,"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,"on_screen":false,"help_text":"text/html","role_description":"text field","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1411053327156702455
|
-5471571624157094370
|
visual_change
|
accessibility
|
NULL
|
Workspace associated with branch 'master' Workspace associated with branch 'master' has been restored
text/html
text/html
text/html
Rollback
Configure…
More
Checked out master
text/html
text/html
text/html
text/html...
|
70869
|
NULL
|
NULL
|
NULL
|
|
70870
|
2544
|
25
|
2026-05-22T10:46:17.023499+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446777023_m1.jpg...
|
Firefox
|
JY-20963 fix deleted object import by LakyLak · Pu JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12121
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
3445499695894966711
|
4775613902094364805
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Jiminny
Jiminny...
|
70868
|
NULL
|
NULL
|
NULL
|
|
70868
|
2544
|
24
|
2026-05-22T10:46:14.261517+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446774261_m1.jpg...
|
Firefox
|
JY-20963 fix deleted object import by LakyLak · Pu JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12121
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS]
Show options
LakyLak commented 6 minutes ago
LakyLak
LakyLak
commented
6 minutes ago
6 minutes ago
JIRA: JY-20963
JIRA:
JY-20963
JY-20963
Changes:
Changes:
Fix import object if deleted...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (33)","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"33","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (2)","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20963 fix deleted object import #12121 Edit title","depth":13,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20963 fix deleted object import","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12121","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Checks pending","depth":13,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks pending","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20963-fix-import-on-deleted-entity","depth":16,"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963-fix-import-on-deleted-entity","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 13 additions & 1 deletion","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (0)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (2)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"LakyLak commented 6 minutes ago","depth":14,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":16,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"6 minutes ago","depth":15,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"6 minutes ago","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20963","depth":16,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20963","depth":17,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fix import object if deleted","depth":18,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
1998963118527105532
|
-8422158359837748604
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS]
Show options
LakyLak commented 6 minutes ago
LakyLak
LakyLak
commented
6 minutes ago
6 minutes ago
JIRA: JY-20963
JIRA:
JY-20963
JY-20963
Changes:
Changes:
Fix import object if deleted...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
70869
|
2545
|
27
|
2026-05-22T10:46:14.156810+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-22/1779 /Users/lukas/.screenpipe/data/data/2026-05-22/1779446774156_m2.jpg...
|
Firefox
|
JY-20963 fix deleted object import by LakyLak · Pu JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app — Work...
|
1
|
github.com/jiminny/app/pull/12121
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS]
Show options
LakyLak commented 6 minutes ago
LakyLak
LakyLak
commented
6 minutes ago
6 minutes ago
JIRA: JY-20963
JIRA:
JY-20963
JY-20963
Changes:
Changes:
Fix import object if deleted
Add or remove reactions
@LakyLak
JY-20963
JY-20963
fix deleted object import
fix deleted object import
7 / 10 checks OK
0c151a1
0c151a1
This branch has not been deployed
This branch has not been deployed
No deployments
Merge info
Merge info
Review required
Review required
At least 1 approving review is required by reviewers with write access.
Some checks haven't completed yet
Some checks haven't completed yet
2 pending, 1 in progress, 1 expected, 7 successful checks
Collapse checks
Collapse 3 pending checks group
3 pending checks
Checks settings
pending checks
pending checks
ci/circleci: test
ci/circleci: test...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.0518755,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.06304868,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":4,"bounds":{"left":0.0,"top":0.08459697,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.09577015,"width":0.15259309,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":4,"bounds":{"left":0.0,"top":0.11731844,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Service-Desk - Queues - Platform team - Service space - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.12849163,"width":0.10721409,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.15003991,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.16121309,"width":0.15791224,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.18276137,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.19393456,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.18994413,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.21548285,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.22665602,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.2482043,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.25937748,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.28092578,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.29209897,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":4,"bounds":{"left":0.0,"top":0.31364724,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20879] Enable users to use their new activity types - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.32482043,"width":0.106715426,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Sentry error","depth":4,"bounds":{"left":0.0028257978,"top":0.35155627,"width":0.024601065,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37071028,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38188347,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":4,"bounds":{"left":0.0,"top":0.40343177,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20813] Twilio number is not released when Voice role is removed from user - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.41460496,"width":0.14793883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.43774942,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Skip to content","depth":6,"bounds":{"left":0.07962101,"top":0.0518755,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":7,"bounds":{"left":0.07962101,"top":0.05347167,"width":0.0029920214,"height":0.21468475},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open menu","depth":10,"bounds":{"left":0.08494016,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Homepage (g then d)","depth":9,"bounds":{"left":0.099567816,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"jiminny","depth":12,"bounds":{"left":0.112865694,"top":0.06464485,"width":0.018949468,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"jiminny","depth":14,"bounds":{"left":0.11486037,"top":0.07063048,"width":0.014960106,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app","depth":12,"bounds":{"left":0.13680187,"top":0.06464485,"width":0.017785905,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":14,"bounds":{"left":0.13879654,"top":0.07063048,"width":0.008477394,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Search or jump to…","depth":9,"bounds":{"left":0.81698805,"top":0.06464485,"width":0.06565824,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Type","depth":12,"bounds":{"left":0.82928854,"top":0.07063048,"width":0.011801862,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"/","depth":12,"bounds":{"left":0.8424202,"top":0.07222666,"width":0.002493351,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"to search","depth":12,"bounds":{"left":0.84640956,"top":0.07063048,"width":0.021276595,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat with Copilot","depth":10,"bounds":{"left":0.88464093,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Open Copilot…","depth":9,"bounds":{"left":0.8949468,"top":0.06464485,"width":0.008643617,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Create new...","depth":9,"bounds":{"left":0.9115692,"top":0.06464485,"width":0.01662234,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"All issues(g then i)","depth":9,"bounds":{"left":0.93085104,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All pull requests","depth":9,"bounds":{"left":0.94414896,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"All repositories","depth":9,"bounds":{"left":0.9574468,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"You have unread notifications(g then n)","depth":9,"bounds":{"left":0.97074467,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Open user navigation menu","depth":9,"bounds":{"left":0.9840425,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Repository navigation","depth":9,"bounds":{"left":0.079288565,"top":0.051077414,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Repository navigation","depth":10,"bounds":{"left":0.079288565,"top":0.05387071,"width":0.0787899,"height":0.023144454},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Code","depth":12,"bounds":{"left":0.08494016,"top":0.09936153,"width":0.025099734,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Code","depth":14,"bounds":{"left":0.095744684,"top":0.10574621,"width":0.011469414,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Pull requests (33)","depth":12,"bounds":{"left":0.11269947,"top":0.09936153,"width":0.05518617,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pull requests","depth":14,"bounds":{"left":0.12333777,"top":0.10574621,"width":0.02925532,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.15525267,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"33","depth":14,"bounds":{"left":0.15824468,"top":0.113727055,"width":0.005817819,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.1640625,"top":0.113727055,"width":0.0016622341,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Agents","depth":12,"bounds":{"left":0.1705452,"top":0.09936153,"width":0.029089095,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Agents","depth":14,"bounds":{"left":0.18151596,"top":0.10574621,"width":0.014960106,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Actions","depth":12,"bounds":{"left":0.20229389,"top":0.09936153,"width":0.03025266,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Actions","depth":14,"bounds":{"left":0.21326463,"top":0.10574621,"width":0.016123671,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Wiki","depth":12,"bounds":{"left":0.23520611,"top":0.09936153,"width":0.022938829,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Wiki","depth":14,"bounds":{"left":0.24601063,"top":0.10574621,"width":0.009142287,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Security and quality (2)","depth":12,"bounds":{"left":0.26080453,"top":0.09936153,"width":0.06798537,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Security and quality","depth":14,"bounds":{"left":0.27260637,"top":0.10574621,"width":0.04255319,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":14,"bounds":{"left":0.31898272,"top":0.113727055,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":14,"bounds":{"left":0.32197472,"top":0.113727055,"width":0.0026595744,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":14,"bounds":{"left":0.3246343,"top":0.113727055,"width":0.0018284575,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Insights","depth":12,"bounds":{"left":0.33144948,"top":0.09936153,"width":0.031083776,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Insights","depth":14,"bounds":{"left":0.34258643,"top":0.10574621,"width":0.016788565,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Settings","depth":12,"bounds":{"left":0.36519283,"top":0.09936153,"width":0.032081116,"height":0.026336791},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Settings","depth":14,"bounds":{"left":0.37649602,"top":0.10574621,"width":0.01761968,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JY-20963 fix deleted object import #12121 Edit title","depth":13,"bounds":{"left":0.33776596,"top":0.15123703,"width":0.20777926,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JY-20963 fix deleted object import","depth":14,"bounds":{"left":0.33776596,"top":0.15203512,"width":0.16057181,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"#","depth":15,"bounds":{"left":0.50099736,"top":0.15203512,"width":0.006482713,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"12121","depth":15,"bounds":{"left":0.50748,"top":0.15203512,"width":0.026097074,"height":0.030327214},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Edit title","depth":14,"bounds":{"left":0.5349069,"top":0.15442938,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Checks pending","depth":13,"bounds":{"left":0.6590758,"top":0.15762171,"width":0.052027926,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks pending","depth":15,"bounds":{"left":0.67137635,"top":0.16360734,"width":0.035405584,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Code","depth":13,"bounds":{"left":0.7137633,"top":0.15762171,"width":0.02825798,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Code","depth":15,"bounds":{"left":0.7180851,"top":0.16360734,"width":0.011635638,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Open","depth":13,"bounds":{"left":0.34840426,"top":0.19553073,"width":0.011968086,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":15,"bounds":{"left":0.36702126,"top":0.19233839,"width":0.018450798,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":16,"bounds":{"left":0.36702126,"top":0.19393456,"width":0.018450798,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"wants to merge 1 commit into","depth":15,"bounds":{"left":0.38680187,"top":0.19393456,"width":0.06349734,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"master","depth":15,"bounds":{"left":0.45162898,"top":0.19193934,"width":0.018284574,"height":0.017557861},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"master","depth":16,"bounds":{"left":0.45362368,"top":0.19513169,"width":0.014295213,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"from","depth":16,"bounds":{"left":0.47124335,"top":0.19393456,"width":0.009973404,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20963-fix-import-on-deleted-entity","depth":16,"bounds":{"left":0.48254654,"top":0.19193934,"width":0.09275266,"height":0.017557861},"on_screen":true,"role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963-fix-import-on-deleted-entity","depth":17,"bounds":{"left":0.48454124,"top":0.19513169,"width":0.0887633,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Copy head branch name to clipboard","depth":16,"bounds":{"left":0.576629,"top":0.1895451,"width":0.00930851,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lines changed: 13 additions & 1 deletion","depth":14,"bounds":{"left":0.71326464,"top":0.24581006,"width":0.019946808,"height":0.11412609},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Conversation (0)","depth":16,"bounds":{"left":0.33776596,"top":0.22785315,"width":0.0546875,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Conversation","depth":17,"bounds":{"left":0.35006648,"top":0.23743017,"width":0.028091755,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.38813165,"top":0.23743017,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"0","depth":18,"bounds":{"left":0.39112368,"top":0.23743017,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.3941157,"top":0.23743017,"width":0.0016622341,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Commits (1)","depth":16,"bounds":{"left":0.39245346,"top":0.22785315,"width":0.04504654,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Commits","depth":17,"bounds":{"left":0.40475398,"top":0.23743017,"width":0.019115692,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.4331782,"top":0.23743017,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"bounds":{"left":0.43617022,"top":0.23743017,"width":0.0021609042,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.43833113,"top":0.23743017,"width":0.0016622341,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Checks (2)","depth":16,"bounds":{"left":0.4375,"top":0.22785315,"width":0.042386968,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Checks","depth":17,"bounds":{"left":0.44980052,"top":0.23743017,"width":0.015957447,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.47556517,"top":0.23743017,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":18,"bounds":{"left":0.47855717,"top":0.23743017,"width":0.0026595744,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.48121676,"top":0.23743017,"width":0.0018284575,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Files changed (1)","depth":16,"bounds":{"left":0.47988698,"top":0.22785315,"width":0.055684842,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Files changed","depth":17,"bounds":{"left":0.4921875,"top":0.23743017,"width":0.029753989,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"(","depth":18,"bounds":{"left":0.53125,"top":0.23743017,"width":0.0029920214,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1","depth":18,"bounds":{"left":0.53424203,"top":0.23743017,"width":0.0021609042,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":")","depth":18,"bounds":{"left":0.53640294,"top":0.23743017,"width":0.0016622341,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Conversation","depth":12,"bounds":{"left":0.33776596,"top":0.27334398,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Conversation","depth":13,"bounds":{"left":0.33776596,"top":0.27613726,"width":0.048204787,"height":0.023144454},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.33776596,"top":0.27334398,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Show options","depth":15,"bounds":{"left":0.61136967,"top":0.27414206,"width":0.007978723,"height":0.02952913},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"LakyLak commented 6 minutes ago","depth":14,"bounds":{"left":0.3620346,"top":0.27414206,"width":0.24135639,"height":0.02952913},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"LakyLak","depth":16,"bounds":{"left":0.3620346,"top":0.2821229,"width":0.018450798,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"LakyLak","depth":17,"bounds":{"left":0.3620346,"top":0.2821229,"width":0.018450798,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"commented","depth":15,"bounds":{"left":0.38181517,"top":0.2821229,"width":0.025598405,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"6 minutes ago","depth":15,"bounds":{"left":0.40874335,"top":0.28052673,"width":0.030585106,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"6 minutes ago","depth":17,"bounds":{"left":0.40874335,"top":0.2821229,"width":0.030585106,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"JIRA: JY-20963","depth":16,"bounds":{"left":0.3620346,"top":0.31723863,"width":0.25731382,"height":0.017557861},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"JIRA:","depth":17,"bounds":{"left":0.3620346,"top":0.31763768,"width":0.015791224,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JY-20963","depth":17,"bounds":{"left":0.3778258,"top":0.31763768,"width":0.027759308,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963","depth":18,"bounds":{"left":0.3778258,"top":0.31763768,"width":0.027759308,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Changes:","depth":16,"bounds":{"left":0.3620346,"top":0.35395053,"width":0.25731382,"height":0.01396648},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Changes:","depth":17,"bounds":{"left":0.3620346,"top":0.35395053,"width":0.021110373,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Fix import object if deleted","depth":18,"bounds":{"left":0.3700133,"top":0.38228253,"width":0.057679523,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Add or remove reactions","depth":16,"bounds":{"left":0.3620346,"top":0.4102155,"width":0.008643617,"height":0.0207502},"on_screen":true,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"@LakyLak","depth":12,"bounds":{"left":0.3700133,"top":0.47406226,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"JY-20963","depth":14,"bounds":{"left":0.37865692,"top":0.47765362,"width":0.019115692,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963","depth":15,"bounds":{"left":0.37865692,"top":0.47765362,"width":0.019115692,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"fix deleted object import","depth":14,"bounds":{"left":0.40009972,"top":0.47765362,"width":0.06000665,"height":0.011572227},"on_screen":true,"help_text":"JY-20963 fix deleted object import","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"fix deleted object import","depth":15,"bounds":{"left":0.40009972,"top":0.47765362,"width":0.06000665,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"7 / 10 checks OK","depth":14,"bounds":{"left":0.60139626,"top":0.47406226,"width":0.005319149,"height":0.016759777},"on_screen":true,"help_text":"","role_description":"summary","subrole":"AXSummary","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"0c151a1","depth":14,"bounds":{"left":0.6080452,"top":0.47765362,"width":0.016954787,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"0c151a1","depth":15,"bounds":{"left":0.6080452,"top":0.47765362,"width":0.016954787,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"This branch has not been deployed","depth":14,"bounds":{"left":0.375,"top":0.5363129,"width":0.2443484,"height":0.017956903},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"This branch has not been deployed","depth":15,"bounds":{"left":0.375,"top":0.53790903,"width":0.08843085,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"No deployments","depth":14,"bounds":{"left":0.375,"top":0.55586594,"width":0.03274601,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Merge info","depth":12,"bounds":{"left":0.35638297,"top":0.5957702,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Merge info","depth":13,"bounds":{"left":0.35638297,"top":0.59856343,"width":0.023105053,"height":0.0518755},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Review required","depth":13,"bounds":{"left":0.37533244,"top":0.60933757,"width":0.24401596,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Review required","depth":14,"bounds":{"left":0.37533244,"top":0.6113328,"width":0.04055851,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"At least 1 approving review is required by reviewers with write access.","depth":14,"bounds":{"left":0.37533244,"top":0.6300878,"width":0.14960106,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Some checks haven't completed yet","depth":13,"bounds":{"left":0.37533244,"top":0.6715882,"width":0.23337767,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Some checks haven't completed yet","depth":14,"bounds":{"left":0.37533244,"top":0.6735834,"width":0.09125665,"height":0.015163607},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2 pending, 1 in progress, 1 expected, 7 successful checks","depth":14,"bounds":{"left":0.37533244,"top":0.6923384,"width":0.12350399,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Collapse checks","depth":13,"bounds":{"left":0.3567154,"top":0.65881884,"width":0.2679521,"height":0.061452515},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Collapse 3 pending checks group","depth":16,"bounds":{"left":0.359375,"top":0.7290503,"width":0.04504654,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXStaticText","text":"3 pending checks","depth":18,"bounds":{"left":0.36236703,"top":0.73423785,"width":0.03374335,"height":0.011971269},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXMenuButton","text":"Checks settings","depth":16,"bounds":{"left":0.60738033,"top":0.7274541,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXHeading","text":"pending checks","depth":19,"bounds":{"left":0.359375,"top":0.7529928,"width":0.0003324468,"height":0.0007980846},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"pending checks","depth":20,"bounds":{"left":0.359375,"top":0.7569832,"width":0.03939495,"height":0.06863528},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"ci/circleci: test","depth":22,"bounds":{"left":0.3806516,"top":0.7601756,"width":0.031083776,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXLink","text":"ci/circleci: test","depth":23,"bounds":{"left":0.3806516,"top":0.7601756,"width":0.031083776,"height":0.013567438},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
507890682975041559
|
-8424405753048313148
|
click
|
accessibility
|
NULL
|
Platform Sprint 4 Q2 - Platform Team - Scrum Board Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 4 Q2 - Platform Team - Scrum Board - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
[JY-20912] Fallback mechanism for users with active SF tokens for CRM Matching - Jira
Service-Desk - Queues - Platform team - Service space - Jira
Service-Desk - Queues - Platform team - Service space - Jira
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20676 delete AJ reports related objects by LakyLak · Pull Request #12098 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
Close tab
Pipelines - jiminny/app
Pipelines - jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST [URL_WITH_CREDENTIALS]
Show options
LakyLak commented 6 minutes ago
LakyLak
LakyLak
commented
6 minutes ago
6 minutes ago
JIRA: JY-20963
JIRA:
JY-20963
JY-20963
Changes:
Changes:
Fix import object if deleted
Add or remove reactions
@LakyLak
JY-20963
JY-20963
fix deleted object import
fix deleted object import
7 / 10 checks OK
0c151a1
0c151a1
This branch has not been deployed
This branch has not been deployed
No deployments
Merge info
Merge info
Review required
Review required
At least 1 approving review is required by reviewers with write access.
Some checks haven't completed yet
Some checks haven't completed yet
2 pending, 1 in progress, 1 expected, 7 successful checks
Collapse checks
Collapse 3 pending checks group
3 pending checks
Checks settings
pending checks
pending checks
ci/circleci: test
ci/circleci: test...
|
NULL
|
NULL
|
NULL
|
NULL
|