|
9873
|
445
|
9
|
2026-05-08T13:39:32.289090+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-08/1778 /Users/lukas/.screenpipe/data/data/2026-05-08/1778247572289_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, 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
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Sync Changes
Hide This Notification
Code changed:
Hide
62
32
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
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":"JY-20725-handle-HS-search-rate-limit, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","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":"19","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":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","depth":4,"on_screen":true,"value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}","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":"62","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXStaticText","text":"32","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\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"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}]...
|
-4321081535914644542
|
5036038088370227430
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20725-handle-HS-search Project: faVsco.js, menu
JY-20725-handle-HS-search-rate-limit, 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
19
Previous Highlighted Error
Next Highlighted Error
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-[IP_ADDRESS]-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}
Sync Changes
Hide This Notification
Code changed:
Hide
62
32
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2839
|
114
|
34
|
2026-05-07T11:43:01.971562+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778154181971_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
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.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","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":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"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}]...
|
-8046841189839988101
|
-4187333948484548378
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
2837
|
NULL
|
NULL
|
NULL
|
|
2841
|
114
|
35
|
2026-05-07T11:43:03.305261+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778154183305_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack, folder
Teams, folder
Tracks, folder
Transcription, folder
Twilio, folder
Users, folder
Vocabulary, folder
Zoom, folder
Command.php
CreateDatabaseUsers.php
DatabaseTableCount.php
DeleteOldAiCrmNotesCommand.php
DeleteS3LeftoversCommand.php
DevPostmanCommand.php
DiarizeViaAiParticipantIdentificationCommand.php
EncryptTokensCommand.php
EngagementStatsRegenerateCommand.php
FeatureFlagsHelper.php
FixCrossTenantIssues.php
FlushRolesPermissionsCache.php
GenerateInternalWebhookToken.php
GroupSetDefaultLanguageCommand.php
HelperTruncateCoachingTables.php
HubspotJournalPollingCommand.php
HubspotWebhookServiceCommand.php
ImportRecording.php
ImportUsersFromCsvFile.php
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php
JiminnySetEncryptedTokenManagerModeCommand.php
JiminnyTokenInfoCommand.php
MakeSlackLiveCoachingChatNotesOn.php
ManageScimForTeam.php
MarkBranchForEnvironmentPipelineCommand.php
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class...
|
[{"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.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","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":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"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":"Analyzing…","depth":4,"bounds":{"left":0.5053192,"top":0.17478053,"width":0.019946808,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"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, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","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":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"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":"Exceptions, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console, folder","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"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddRateLimitCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixHubSpotTokens.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixMissMatchedCrmActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MonitorSocialAccountsState.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"on_screen":false,"role_description":"text"}]...
|
-5207249351467171980
|
5036074921934260454
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack, folder
Teams, folder
Tracks, folder
Transcription, folder
Twilio, folder
Users, folder
Vocabulary, folder
Zoom, folder
Command.php
CreateDatabaseUsers.php
DatabaseTableCount.php
DeleteOldAiCrmNotesCommand.php
DeleteS3LeftoversCommand.php
DevPostmanCommand.php
DiarizeViaAiParticipantIdentificationCommand.php
EncryptTokensCommand.php
EngagementStatsRegenerateCommand.php
FeatureFlagsHelper.php
FixCrossTenantIssues.php
FlushRolesPermissionsCache.php
GenerateInternalWebhookToken.php
GroupSetDefaultLanguageCommand.php
HelperTruncateCoachingTables.php
HubspotJournalPollingCommand.php
HubspotWebhookServiceCommand.php
ImportRecording.php
ImportUsersFromCsvFile.php
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php
JiminnySetEncryptedTokenManagerModeCommand.php
JiminnyTokenInfoCommand.php
MakeSlackLiveCoachingChatNotesOn.php
ManageScimForTeam.php
MarkBranchForEnvironmentPipelineCommand.php
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2842
|
113
|
38
|
2026-05-07T11:43:03.885891+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778154183885_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class...
|
[{"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","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":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"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":"Analyzing…","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"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},{"role":"AXStaticText","text":"app ~/jiminny/app, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","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":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"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":"Exceptions, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console, folder","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"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
-7650338153608560940
|
5036038088437336294
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
2843
|
114
|
36
|
2026-05-07T11:43:04.407014+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778154184407_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack, folder
Teams, folder
Tracks, folder
Transcription, folder
Twilio, folder
Users, folder
Vocabulary, folder
Zoom, folder
Command.php
CreateDatabaseUsers.php
DatabaseTableCount.php
DeleteOldAiCrmNotesCommand.php
DeleteS3LeftoversCommand.php
DevPostmanCommand.php
DiarizeViaAiParticipantIdentificationCommand.php
EncryptTokensCommand.php
EngagementStatsRegenerateCommand.php
FeatureFlagsHelper.php
FixCrossTenantIssues.php
FlushRolesPermissionsCache.php
GenerateInternalWebhookToken.php
GroupSetDefaultLanguageCommand.php
HelperTruncateCoachingTables.php
HubspotJournalPollingCommand.php
HubspotWebhookServiceCommand.php
ImportRecording.php
ImportUsersFromCsvFile.php
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php
JiminnySetEncryptedTokenManagerModeCommand.php
JiminnyTokenInfoCommand.php
MakeSlackLiveCoachingChatNotesOn.php
ManageScimForTeam.php
MarkBranchForEnvironmentPipelineCommand.php
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php
RemoveExpiredNudgesCommand.php
RemoveUnusedParticipantSpeechesCommand.php
ResetElasticSearch.php
RestoreActivityCrmProviderIdCommand.php
RestoreActivityTypeCommand.php
RunAiCallScoringForUntypedActivitiesCommand.php
SeedActivities.php
SendNudgeExpirationWarningsCommand.php
SyncActivity.php, class
TrackImported.php
WhichWorkerIsWorkingOnWhichJob.php
Scheduling, folder
Kernel.php
Contracts, folder
Acl, folder
ActivitySearch, folder
AiAutomation, folder
Crm, folder
DateTime, folder
ES, folder
Http, folder
Requests, folder
ApiResponse.php
RateLimited.php
RateLimitInterface.php
Interactions, folder
Model, folder
Nudge, folder
Playlist, folder
Repositories, folder
Services, folder
User, 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.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","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":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"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":"Analyzing…","depth":4,"bounds":{"left":0.5053192,"top":0.17478053,"width":0.019946808,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"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, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","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":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"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":"Exceptions, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console, folder","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"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddRateLimitCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixHubSpotTokens.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixMissMatchedCrmActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MonitorSocialAccountsState.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSyncBatchesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RecalculateDealRisksCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveDeleteMarkersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveExpiredNudgesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveUnusedParticipantSpeechesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetElasticSearch.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityCrmProviderIdCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityTypeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RunAiCallScoringForUntypedActivitiesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SeedActivities.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNudgeExpirationWarningsCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TrackImported.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"WhichWorkerIsWorkingOnWhichJob.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Scheduling, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kernel.php","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Contracts, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ES, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Http, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Requests, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ApiResponse.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimited.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitInterface.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Interactions, 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":"Nudge, 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":"Repositories, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Services, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"User, folder","depth":9,"on_screen":false,"role_description":"text"}]...
|
4954725836517024779
|
5036074917639293158
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack, folder
Teams, folder
Tracks, folder
Transcription, folder
Twilio, folder
Users, folder
Vocabulary, folder
Zoom, folder
Command.php
CreateDatabaseUsers.php
DatabaseTableCount.php
DeleteOldAiCrmNotesCommand.php
DeleteS3LeftoversCommand.php
DevPostmanCommand.php
DiarizeViaAiParticipantIdentificationCommand.php
EncryptTokensCommand.php
EngagementStatsRegenerateCommand.php
FeatureFlagsHelper.php
FixCrossTenantIssues.php
FlushRolesPermissionsCache.php
GenerateInternalWebhookToken.php
GroupSetDefaultLanguageCommand.php
HelperTruncateCoachingTables.php
HubspotJournalPollingCommand.php
HubspotWebhookServiceCommand.php
ImportRecording.php
ImportUsersFromCsvFile.php
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php
JiminnySetEncryptedTokenManagerModeCommand.php
JiminnyTokenInfoCommand.php
MakeSlackLiveCoachingChatNotesOn.php
ManageScimForTeam.php
MarkBranchForEnvironmentPipelineCommand.php
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php
RemoveExpiredNudgesCommand.php
RemoveUnusedParticipantSpeechesCommand.php
ResetElasticSearch.php
RestoreActivityCrmProviderIdCommand.php
RestoreActivityTypeCommand.php
RunAiCallScoringForUntypedActivitiesCommand.php
SeedActivities.php
SendNudgeExpirationWarningsCommand.php
SyncActivity.php, class
TrackImported.php
WhichWorkerIsWorkingOnWhichJob.php
Scheduling, folder
Kernel.php
Contracts, folder
Acl, folder
ActivitySearch, folder
AiAutomation, folder
Crm, folder
DateTime, folder
ES, folder
Http, folder
Requests, folder
ApiResponse.php
RateLimited.php
RateLimitInterface.php
Interactions, folder
Model, folder
Nudge, folder
Playlist, folder
Repositories, folder
Services, folder
User, folder...
|
2841
|
NULL
|
NULL
|
NULL
|
|
2844
|
113
|
39
|
2026-05-07T11:43:04.828615+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778154184828_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack, folder
Teams, folder
Tracks, folder
Transcription, folder
Twilio, folder
Users, folder
Vocabulary, folder
Zoom, folder
Command.php
CreateDatabaseUsers.php
DatabaseTableCount.php
DeleteOldAiCrmNotesCommand.php
DeleteS3LeftoversCommand.php
DevPostmanCommand.php
DiarizeViaAiParticipantIdentificationCommand.php
EncryptTokensCommand.php
EngagementStatsRegenerateCommand.php
FeatureFlagsHelper.php
FixCrossTenantIssues.php
FlushRolesPermissionsCache.php
GenerateInternalWebhookToken.php
GroupSetDefaultLanguageCommand.php
HelperTruncateCoachingTables.php
HubspotJournalPollingCommand.php
HubspotWebhookServiceCommand.php
ImportRecording.php
ImportUsersFromCsvFile.php
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php
JiminnySetEncryptedTokenManagerModeCommand.php
JiminnyTokenInfoCommand.php
MakeSlackLiveCoachingChatNotesOn.php
ManageScimForTeam.php
MarkBranchForEnvironmentPipelineCommand.php
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php
RemoveExpiredNudgesCommand.php
RemoveUnusedParticipantSpeechesCommand.php
ResetElasticSearch.php
RestoreActivityCrmProviderIdCommand.php
RestoreActivityTypeCommand.php
RunAiCallScoringForUntypedActivitiesCommand.php
SeedActivities.php
SendNudgeExpirationWarningsCommand.php
SyncActivity.php, class...
|
[{"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","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":"AXTextArea","text":"Editor for custom.log","depth":4,"on_screen":true,"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":"Analyzing…","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\ServiceTraits;\n\nuse Carbon\\Carbon;\nuse Exception;\nuse Illuminate\\Support\\Str;\nuse Jiminny\\Exceptions\\CrmException;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteAccountJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteContactJob;\nuse Jiminny\\Jobs\\Crm\\Delete\\DeleteOpportunityJob;\nuse Jiminny\\Models\\Account;\nuse Jiminny\\Models\\Contact;\nuse Jiminny\\Models\\Lead;\nuse Jiminny\\Repositories\\Crm\\CrmEntityRepository;\nuse Jiminny\\Services\\Avatar\\ProspectPhotoPathService;\nuse Jiminny\\Services\\Crm\\Hubspot\\HubspotClientInterface;\nuse Jiminny\\Services\\Crm\\Hubspot\\WebhookSyncBatchProcessor;\nuse Jiminny\\Utils\\StringUtil;\n\ntrait SyncCrmEntitiesTrait\n{\n use OpportunitySyncTrait;\n private const string CDN_URL = 'https://cdn2.hubspot.net/';\n protected CrmEntityRepository $crmEntityRepository;\n protected ProspectPhotoPathService $prospectPhotoPathService;\n\n private function getAssociationDataForCollection(array $collection, string $fromObject, string $toObject): array\n {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $hsOpportunityIds = array_column($collection, 'id');\n\n return $this->client->getAssociationsData($hsOpportunityIds, $fromObject, $toObject);\n }\n\n private function importAssociationData(array $collection, array $associatedData): array\n {\n $data = [];\n if (! empty($associatedData[$collection['id']])) {\n foreach ($associatedData[$collection['id']] as $id) {\n $data[] = [\n 'id' => $id,\n ];\n }\n }\n\n return ['results' => $data];\n }\n\n /**\n * Sync contacts modified since a given date (manual sync mode).\n *\n * This method fetches contacts from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-contact with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncContacts is used:\n *\n * @param Carbon $since Fetch contacts modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of contacts successfully synced\n */\n public function syncContacts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {\n $this->importContact($hsContact);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncContact(string $crmId): ?Contact\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getContactFields();\n $hsContact = $this->client->getContactById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Contacts\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n if (empty($hsContact['properties']) || empty($hsContact['id'])) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'has_properties' => ! empty($hsContact['properties']),\n 'has_id' => ! empty($hsContact['id']),\n ]);\n\n return null;\n }\n\n return $this->importContact($hsContact);\n }\n\n private function getContactFields(): array\n {\n return [\n 'associatedcompanyid',\n 'country',\n 'firstname',\n 'lastname',\n 'phone',\n 'mobilephone',\n 'email',\n 'photo',\n 'hs_avatar_filemanager_key',\n 'jobtitle',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n /**\n * @inheritdoc\n */\n private function importContact($crmData, array $accountMappings = []): ?Contact\n {\n $crmProviderId = $crmData['id'] ?? null;\n\n $this->logger->info('[HubSpot] importContact', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importContact failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $crmData['id'];\n\n $accountId = $this->resolveContactAccount($properties, $accountMappings);\n $data = $this->buildContactData($crmId, $properties, $accountId);\n\n return $this->crmEntityRepository->importContact($this->config, $data);\n }\n\n private function resolveContactAccount(array $properties, array $accountMappings): ?int\n {\n if (empty($properties['associatedcompanyid'])) {\n return null;\n }\n\n $companyId = (string) $properties['associatedcompanyid'];\n\n if (! empty($accountMappings)) {\n return $accountMappings[$companyId] ?? null;\n }\n\n return $this->crmEntityRepository->findAccountByExternalId(\n $this->team->getCrmConfiguration(),\n $companyId\n )?->getId() ?? $this->syncAccount($companyId)?->getId();\n }\n\n private function buildContactData(string $crmId, array $properties, ?int $accountId): array\n {\n $countryCode = $this->buildContactCountry($properties);\n $name = $this->buildContactName($properties);\n $photoPath = $this->teamService->generateAvatar(\n $crmId,\n empty($name) ? ($properties['email'] ?? 'N/A') : $name,\n );\n $parsedNumber = $this->buildContactPhone($countryCode, $properties);\n $mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);\n\n $ownerId = $properties['hubspot_owner_id'] ?? null;\n $profile = $ownerId !== null\n ? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)\n : null;\n\n $ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)\n ? $parsedNumber['ext']\n : null;\n\n $title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;\n $email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;\n $remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;\n\n return [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->getId(),\n 'account_id' => $accountId,\n 'user_id' => $profile?->getUserId(),\n 'owner_id' => $ownerId,\n 'name' => $name,\n 'title' => $title,\n 'email' => $email,\n 'country_code' => $countryCode,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'mobile_phone' => $mobileNumber ?? null,\n 'ext' => $ext,\n 'photo_path' => $photoPath,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n }\n\n /**\n * @param $properties\n */\n private function buildContactName($properties): string\n {\n if (is_array($properties)) {\n return $this->buildContactNameFromArray($properties);\n }\n\n return $this->buildContactNameFromObject($properties);\n }\n\n private function buildContactNameFromArray(array $properties): string\n {\n if (! empty($properties['name'])) {\n return mb_strimwidth($properties['name'], 0, 100);\n }\n\n $name = '';\n if (! empty($properties['firstname'])) {\n $name = $properties['firstname'] . ' ';\n }\n\n if (! empty($properties['lastname'])) {\n $name .= $properties['lastname'];\n }\n\n if ($name === '' && ! empty($properties['email'])) {\n $name = $properties['email'];\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n private function buildContactNameFromObject($properties): string\n {\n $name = '';\n if (isset($properties->firstname)) {\n $name = $properties->firstname->value . ' ';\n }\n if (isset($properties->lastname)) {\n $name .= $properties->lastname->value;\n }\n if ($name === '' && isset($properties->email)) {\n $name = $properties->email->value;\n }\n\n return mb_strimwidth($name, 0, 100);\n }\n\n /**\n * @param $properties\n */\n private function buildContactPhone(?string $countryCode, $properties): ?array\n {\n if (is_array($properties) && empty($properties['phone']) === false) {\n $number = mb_strimwidth($properties['phone'], 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n } elseif (isset($properties->phone)) {\n $number = mb_strimwidth($properties->phone->value, 0, 25);\n\n return parsePhoneNumber($countryCode, $number);\n }\n\n return [];\n }\n\n /**\n * @param $properties\n */\n private function buildContactMobilePhone(?string $countryCode, $properties): ?string\n {\n return isset($properties['mobilephone'])\n ? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')\n : null;\n }\n\n /**\n * @param $properties\n * @param $account\n */\n private function buildContactCountry($properties): ?string\n {\n if (is_array($properties) && empty($properties['country']) === false) {\n return $this->convertCountryNameToCode($properties['country']);\n }\n\n if (isset($properties->country)) {\n return $this->convertCountryNameToCode($properties->country->value);\n }\n\n return null;\n }\n\n /**\n * HubSpot doesn't have leads, so this method does nothing.\n *\n * @param Carbon $since\n * @param Carbon|null $to\n * @param string|null $crmProfileId\n *\n * @return int\n */\n public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int\n {\n // Mark unused parameters to avoid code smell warnings\n unset($since, $to, $crmProfileId);\n\n return 0;\n }\n\n /**\n * HubSpot doesn't have leads.\n *\n * @param string $crmId\n *\n * @inheritdoc\n */\n public function syncLead(string $crmId): ?Lead\n {\n // Mark unused parameter to avoid code smell warnings\n unset($crmId);\n\n return null;\n }\n\n /**\n * Sync accounts (companies) modified since a given date (manual sync mode).\n *\n * This method fetches companies from HubSpot API based on modification date and\n * imports them one by one. It is used for:\n * - Manual sync commands (e.g., crm:sync-account with --from parameter)\n * - Initial sync for new teams\n * - Backfill operations\n *\n * For regular sync webhook batchSyncCompanies is used:\n *\n * @param Carbon $since Fetch companies modified after this date\n * @param Carbon|null $to Optional end date for modification range\n *\n * @return int Number of companies successfully synced\n */\n public function syncAccounts(Carbon $since, ?Carbon $to = null): int\n {\n $syncCount = 0;\n\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);\n\n foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {\n $this->importAccount($hsAccount);\n $syncCount++;\n }\n } catch (Exception $exception) {\n $this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [\n 'teamId' => $this->team->getUuid(),\n 'reason' => $exception->getMessage(),\n ]);\n }\n\n return $syncCount;\n }\n\n /**\n * @inheritdoc\n */\n public function syncAccount(string $crmId): ?Account\n {\n try {\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $fields = $this->getCompanyFields();\n $hsAccount = $this->client->getAccountById($crmId, $fields);\n } catch (\\HubSpot\\Client\\Crm\\Companies\\ApiException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n } catch (CrmException $e) {\n $this->logger->info('[' . $this->getDisplayName() . '] Account not found', [\n 'teamId' => $this->team->getUuid(),\n 'crmId' => $crmId,\n 'reason' => $e->getMessage(),\n ]);\n\n return null;\n }\n\n return $this->importAccount($hsAccount);\n }\n\n /**\n * Process webhook-collected contact batches.\n *\n * Drains Redis sets containing contact CRM IDs collected from webhook events\n * and dispatches ImportContactBatch jobs for batch processing.\n *\n * @return int Number of contact IDs dispatched to jobs\n */\n public function batchSyncContacts(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,\n $configId\n );\n }\n\n public function importContactBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowContacts = [];\n\n $fetchStart = microtime(true);\n $allContacts = $this->fetchContactsByIdsInChunks($crmIds);\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allContacts, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allContacts),\n ]);\n }\n\n if (empty($allContacts)) {\n return $result;\n }\n\n $prepareStart = microtime(true);\n $accountMappings = $this->prepareAccountMappingsForContacts($allContacts);\n $prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);\n\n $loopStart = microtime(true);\n foreach ($allContacts as $contactData) {\n $contactStart = microtime(true);\n\n try {\n $contact = $this->importContact($contactData, $accountMappings);\n if ($contact !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $contactData['id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [\n 'teamId' => $this->team->getId(),\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $contactMs = (int) round((microtime(true) - $contactStart) * 1000);\n if ($contactMs > 1000) {\n $slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [\n 'teamId' => $this->team->getId(),\n 'contact_count' => \\count($allContacts),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'prepare_accounts_ms' => $prepareAccountsMs,\n 'contacts_loop_ms' => $loopMs,\n 'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \\count($allContacts)) : 0,\n 'slow_contacts_count' => \\count($slowContacts),\n 'slow_contacts' => array_slice($slowContacts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function fetchContactsByIdsInChunks(array $crmIds): array\n {\n $fields = $this->getContactFields();\n $allContacts = [];\n\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $contacts = $this->client->getContactsByIds($chunk, $fields);\n foreach ($contacts as $contactData) {\n $allContacts[] = $contactData;\n }\n } catch (\\Throwable $e) {\n // @TODO what will happen if this exception is thrown\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n\n return $allContacts;\n }\n\n private function prepareAccountMappingsForContacts(array $contacts): array\n {\n $companyIds = [];\n foreach ($contacts as $contact) {\n $companyId = $contact['properties']['associatedcompanyid'] ?? null;\n if ($companyId !== null && $companyId !== '') {\n $companyIds[] = (string) $companyId;\n }\n }\n\n $companyIds = array_unique($companyIds);\n\n if (empty($companyIds)) {\n return [];\n }\n\n $mappings = $this->crmEntityRepository\n ->getExistingAccountIdsMap($this->config, $companyIds);\n\n $missingCompanyIds = array_diff($companyIds, array_keys($mappings));\n\n if (empty($missingCompanyIds)) {\n return $mappings;\n }\n\n $this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [\n 'teamId' => $this->team->getId(),\n 'total_companies' => \\count($companyIds),\n 'existing_companies' => \\count($mappings),\n 'missing_companies' => \\count($missingCompanyIds),\n ]);\n\n try {\n $syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);\n $mappings = array_merge($mappings, $syncedAccounts);\n } catch (\\Throwable $e) {\n $this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [\n 'teamId' => $this->team->getId(),\n 'missingCompanyIds' => $missingCompanyIds,\n 'missingCount' => count($missingCompanyIds),\n 'error' => $e->getMessage(),\n ]);\n }\n\n return $mappings;\n }\n\n private function batchSyncAccountsForContacts(array $companyIds): array\n {\n $syncedAccounts = [];\n $fields = $this->getCompanyFields();\n\n foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n\n foreach ($companies as $companyData) {\n try {\n $account = $this->importAccount($companyData);\n if ($account) {\n $syncedAccounts[$account->getCrmProviderId()] = $account->getId();\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [\n 'teamId' => $this->team->getId(),\n 'companyId' => $companyData['id'] ?? 'unknown',\n 'error' => $e->getMessage(),\n ]);\n }\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'teamId' => $this->team->getId(),\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n }\n }\n\n return $syncedAccounts;\n }\n\n /**\n * Process webhook-collected company batches.\n *\n * Drains Redis sets containing company CRM IDs collected from webhook events\n * and dispatches ImportAccountBatch jobs for batch processing.\n *\n * @return int Number of company IDs dispatched to jobs\n */\n public function batchSyncCompanies(): int\n {\n $configId = $this->team->getCrmConfiguration()->getId();\n\n return $this->batchProcessor->processBatchesForObjectType(\n WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,\n $configId\n );\n }\n\n public function importAccountBatchByIds(array $crmIds): array\n {\n $result = [\n 'success_count' => 0,\n 'failed_ids' => [],\n 'errors' => [],\n ];\n\n if (! $this->client instanceof HubspotClientInterface) {\n throw new \\InvalidArgumentException('Client must implement HubspotClientInterface');\n }\n\n $batchStart = microtime(true);\n $slowAccounts = [];\n\n $fields = $this->getCompanyFields();\n $allCompanies = [];\n\n $fetchStart = microtime(true);\n foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {\n try {\n $companies = $this->client->getCompaniesByIds($chunk, $fields);\n foreach ($companies as $companyData) {\n $allCompanies[] = $companyData;\n }\n } catch (\\Throwable $e) {\n $this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [\n 'chunk_size' => \\count($chunk),\n 'error' => $e->getMessage(),\n ]);\n\n throw $e;\n }\n }\n $fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);\n\n $fetchedIds = array_map('strval', array_column($allCompanies, 'id'));\n $notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));\n\n if (! empty($notFoundIds)) {\n $this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [\n 'teamId' => $this->team->getId(),\n 'notFoundCount' => \\count($notFoundIds),\n 'notFoundIds' => $notFoundIds,\n 'requestedCount' => \\count($crmIds),\n 'fetchedCount' => \\count($allCompanies),\n ]);\n }\n\n $loopStart = microtime(true);\n foreach ($allCompanies as $companyData) {\n $accountStart = microtime(true);\n\n try {\n $account = $this->importAccount($companyData);\n if ($account !== null) {\n $result['success_count']++;\n }\n } catch (\\Throwable $e) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $result['failed_ids'][] = $crmId;\n $result['errors'][$crmId] = $e->getMessage();\n\n $this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [\n 'crmId' => $crmId,\n 'error' => $e->getMessage(),\n ]);\n }\n\n $accountMs = (int) round((microtime(true) - $accountStart) * 1000);\n if ($accountMs > 1000) {\n $crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';\n $slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];\n }\n }\n $loopMs = (int) round((microtime(true) - $loopStart) * 1000);\n $totalMs = (int) round((microtime(true) - $batchStart) * 1000);\n\n $this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [\n 'teamId' => $this->team->getId(),\n 'account_count' => \\count($allCompanies),\n 'requested_count' => \\count($crmIds),\n 'not_found_count' => \\count($notFoundIds),\n 'total_ms' => $totalMs,\n 'fetch_api_ms' => $fetchMs,\n 'accounts_loop_ms' => $loopMs,\n 'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \\count($allCompanies)) : 0,\n 'slow_accounts_count' => \\count($slowAccounts),\n 'slow_accounts' => array_slice($slowAccounts, 0, 10),\n ]);\n\n return $result;\n }\n\n private function getCompanyFields(): array\n {\n return [\n 'country',\n 'name',\n 'phone',\n 'domain',\n 'hs_avatar_filemanager_key',\n 'industry',\n 'hubspot_owner_id',\n 'hs_object_id',\n 'createdate',\n 'hs_lastmodifieddate',\n ];\n }\n\n private function importAccount($crmData): ?Account\n {\n $crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;\n\n $this->logger->info('[HubSpot] importAccount', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n if (empty($crmData['properties'])) {\n $this->logger->info('[HubSpot] importAccount failed: empty properties', [\n 'crm_provider_id' => $crmProviderId,\n 'config_id' => $this->config->getId(),\n ]);\n\n return null;\n }\n\n $properties = $crmData['properties'];\n $crmId = (string) $properties['hs_object_id'];\n\n $countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;\n\n if (isset($properties['phone'])) {\n // Trim to our width and attempt to parse it.\n $number = mb_strimwidth($properties['phone'], 0, 25);\n $parsedNumber = parsePhoneNumber($countryCode, $number);\n } else {\n $parsedNumber = [];\n }\n\n $name = '[unknown]';\n if (isset($properties['name'])) {\n $name = $properties['name'];\n }\n\n $photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(\n $this->config,\n $crmId,\n Account::class,\n $crmId,\n $name\n );\n\n $industry = null;\n if (isset($properties['industry'])) {\n $industry = mb_strimwidth($properties['industry'], 0, 40);\n }\n\n $ownerId = $profile = null;\n if (isset($properties['hubspot_owner_id'])) {\n $ownerId = $properties['hubspot_owner_id'];\n $profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);\n }\n\n $domain = null;\n if (isset($properties['domain'])) {\n $domain = StringUtil::resolveDomain($properties['domain']);\n }\n\n $remotelyCreatedAt = null;\n if (isset($properties['createdate']) && ! empty($properties['createdate'])) {\n $remotelyCreatedAt = Carbon::parse($properties['createdate']);\n }\n\n $data = [\n 'crm_provider_id' => $crmId,\n 'team_id' => $this->team->id,\n 'user_id' => $profile ? $profile->user_id : null,\n 'owner_id' => $ownerId,\n 'name' => mb_strimwidth($name, 0, 191),\n 'photo_path' => $photoPath,\n 'industry' => $industry,\n 'domain' => $domain !== null\n ? substr($domain, 0, 191)\n : null,\n 'phone' => $parsedNumber['phone'] ?? null,\n 'ext' => $parsedNumber['ext'] ?? null,\n 'country_code' => $countryCode,\n 'remotely_created_at' => $remotelyCreatedAt,\n ];\n\n return $this->crmEntityRepository->importAccount($this->config, $data);\n }\n\n public function deleteContact(string $crmProviderId): bool\n {\n try {\n $contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);\n\n if (! $contact) {\n $this->logger->info('[HubSpot] Contact not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $contact->getId();\n\n $this->logger->info('[HubSpot] Deleting contact via webhook', [\n 'contact_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $contact->delete();\n DeleteContactJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete contact via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteAccount(string $crmProviderId): bool\n {\n try {\n $account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);\n\n if (! $account) {\n $this->logger->info('[HubSpot] Account not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $account->getId();\n\n $this->logger->info('[HubSpot] Deleting account via webhook', [\n 'account_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $account->delete();\n DeleteAccountJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete account via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n\n public function deleteOpportunity(string $crmProviderId): bool\n {\n try {\n $opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);\n\n if (! $opportunity) {\n $this->logger->info('[HubSpot] Opportunity not found for deletion', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n // return success because we do not know which instance is the target\n return true;\n }\n\n $id = $opportunity->getId();\n\n $this->logger->info('[HubSpot] Deleting opportunity via webhook', [\n 'opportunity_id' => $id,\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n ]);\n\n $opportunity->delete();\n DeleteOpportunityJob::dispatch($id)->afterCommit();\n\n return true;\n } catch (Exception $e) {\n $this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [\n 'crm_provider_id' => $crmProviderId,\n 'team_id' => $this->team->getId(),\n 'error' => $e->getMessage(),\n ]);\n\n return false;\n }\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"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},{"role":"AXStaticText","text":"app ~/jiminny/app, folder","depth":6,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".circleci, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".cursor, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".github","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".sonarlint, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".vscode, folder","depth":7,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":".windsurf, folder","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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Component, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Acl, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActionItems, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Activity, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivityAnalytics, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ActivitySearch, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiActivityType, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiAutomation, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AiCallScoring, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnything, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dtos, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Events, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskAnythingPromptService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HistoryService.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AskJiminnyAi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AWS, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BillingManagement, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Cache, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CoachingFeedback, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Country, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CustomerApi, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Database, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Datadog, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DateTime, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealRisks, folder","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","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":"Job, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAware.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimitAwareWrapper.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BotsQueueConstants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Constants.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessingQueueConstants.php","depth":10,"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":"Exceptions, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Service, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BaseRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EfficientJsonParser.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProviderRateLimiter.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RateLimiterInstance.php, class","depth":11,"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, folder","depth":8,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Console, folder","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"},{"role":"AXStaticText","text":"Hubspot, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits, folder","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddRateLimitCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixHubSpotTokens.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixMissMatchedCrmActivitiesCommand.php, final class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportCallsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MonitorSocialAccountsState.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSyncBatchesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RecalculateDealRisksCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveDeleteMarkersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveExpiredNudgesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveUnusedParticipantSpeechesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetElasticSearch.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityCrmProviderIdCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityTypeCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RunAiCallScoringForUntypedActivitiesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SeedActivities.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNudgeExpirationWarningsCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php, class","depth":10,"on_screen":false,"role_description":"text"}]...
|
-395738599667889143
|
5036074921934260454
|
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
Editor for custom.log
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\ServiceTraits;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Jiminny\Exceptions\CrmException;
use Jiminny\Jobs\Crm\Delete\DeleteAccountJob;
use Jiminny\Jobs\Crm\Delete\DeleteContactJob;
use Jiminny\Jobs\Crm\Delete\DeleteOpportunityJob;
use Jiminny\Models\Account;
use Jiminny\Models\Contact;
use Jiminny\Models\Lead;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Avatar\ProspectPhotoPathService;
use Jiminny\Services\Crm\Hubspot\HubspotClientInterface;
use Jiminny\Services\Crm\Hubspot\WebhookSyncBatchProcessor;
use Jiminny\Utils\StringUtil;
trait SyncCrmEntitiesTrait
{
use OpportunitySyncTrait;
private const string CDN_URL = '[URL_WITH_CREDENTIALS] Carbon $since Fetch contacts modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of contacts successfully synced
*/
public function syncContacts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'contacts') as $hsContact) {
$this->importContact($hsContact);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync contacts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncContact(string $crmId): ?Contact
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getContactFields();
$hsContact = $this->client->getContactById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Contacts\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
if (empty($hsContact['properties']) || empty($hsContact['id'])) {
$this->logger->warning('[' . $this->getDisplayName() . '] Contact data incomplete', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'has_properties' => ! empty($hsContact['properties']),
'has_id' => ! empty($hsContact['id']),
]);
return null;
}
return $this->importContact($hsContact);
}
private function getContactFields(): array
{
return [
'associatedcompanyid',
'country',
'firstname',
'lastname',
'phone',
'mobilephone',
'email',
'photo',
'hs_avatar_filemanager_key',
'jobtitle',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
/**
* @inheritdoc
*/
private function importContact($crmData, array $accountMappings = []): ?Contact
{
$crmProviderId = $crmData['id'] ?? null;
$this->logger->info('[HubSpot] importContact', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importContact failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $crmData['id'];
$accountId = $this->resolveContactAccount($properties, $accountMappings);
$data = $this->buildContactData($crmId, $properties, $accountId);
return $this->crmEntityRepository->importContact($this->config, $data);
}
private function resolveContactAccount(array $properties, array $accountMappings): ?int
{
if (empty($properties['associatedcompanyid'])) {
return null;
}
$companyId = (string) $properties['associatedcompanyid'];
if (! empty($accountMappings)) {
return $accountMappings[$companyId] ?? null;
}
return $this->crmEntityRepository->findAccountByExternalId(
$this->team->getCrmConfiguration(),
$companyId
)?->getId() ?? $this->syncAccount($companyId)?->getId();
}
private function buildContactData(string $crmId, array $properties, ?int $accountId): array
{
$countryCode = $this->buildContactCountry($properties);
$name = $this->buildContactName($properties);
$photoPath = $this->teamService->generateAvatar(
$crmId,
empty($name) ? ($properties['email'] ?? 'N/A') : $name,
);
$parsedNumber = $this->buildContactPhone($countryCode, $properties);
$mobileNumber = $this->buildContactMobilePhone($countryCode, $properties);
$ownerId = $properties['hubspot_owner_id'] ?? null;
$profile = $ownerId !== null
? $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId)
: null;
$ext = (isset($parsedNumber['ext']) && is_string($parsedNumber['ext']) && strlen($parsedNumber['ext']) <= 10)
? $parsedNumber['ext']
: null;
$title = isset($properties['jobtitle']) ? mb_strimwidth($properties['jobtitle'], 0, 128) : null;
$email = isset($properties['email']) ? mb_strimwidth($properties['email'], 0, 191) : null;
$remotelyCreatedAt = ! empty($properties['createdate']) ? Carbon::parse($properties['createdate']) : null;
return [
'crm_provider_id' => $crmId,
'team_id' => $this->team->getId(),
'account_id' => $accountId,
'user_id' => $profile?->getUserId(),
'owner_id' => $ownerId,
'name' => $name,
'title' => $title,
'email' => $email,
'country_code' => $countryCode,
'phone' => $parsedNumber['phone'] ?? null,
'mobile_phone' => $mobileNumber ?? null,
'ext' => $ext,
'photo_path' => $photoPath,
'remotely_created_at' => $remotelyCreatedAt,
];
}
/**
* @param $properties
*/
private function buildContactName($properties): string
{
if (is_array($properties)) {
return $this->buildContactNameFromArray($properties);
}
return $this->buildContactNameFromObject($properties);
}
private function buildContactNameFromArray(array $properties): string
{
if (! empty($properties['name'])) {
return mb_strimwidth($properties['name'], 0, 100);
}
$name = '';
if (! empty($properties['firstname'])) {
$name = $properties['firstname'] . ' ';
}
if (! empty($properties['lastname'])) {
$name .= $properties['lastname'];
}
if ($name === '' && ! empty($properties['email'])) {
$name = $properties['email'];
}
return mb_strimwidth($name, 0, 100);
}
private function buildContactNameFromObject($properties): string
{
$name = '';
if (isset($properties->firstname)) {
$name = $properties->firstname->value . ' ';
}
if (isset($properties->lastname)) {
$name .= $properties->lastname->value;
}
if ($name === '' && isset($properties->email)) {
$name = $properties->email->value;
}
return mb_strimwidth($name, 0, 100);
}
/**
* @param $properties
*/
private function buildContactPhone(?string $countryCode, $properties): ?array
{
if (is_array($properties) && empty($properties['phone']) === false) {
$number = mb_strimwidth($properties['phone'], 0, 25);
return parsePhoneNumber($countryCode, $number);
} elseif (isset($properties->phone)) {
$number = mb_strimwidth($properties->phone->value, 0, 25);
return parsePhoneNumber($countryCode, $number);
}
return [];
}
/**
* @param $properties
*/
private function buildContactMobilePhone(?string $countryCode, $properties): ?string
{
return isset($properties['mobilephone'])
? Str::limit(phone_e164($countryCode, $properties['mobilephone']), 25, '')
: null;
}
/**
* @param $properties
* @param $account
*/
private function buildContactCountry($properties): ?string
{
if (is_array($properties) && empty($properties['country']) === false) {
return $this->convertCountryNameToCode($properties['country']);
}
if (isset($properties->country)) {
return $this->convertCountryNameToCode($properties->country->value);
}
return null;
}
/**
* HubSpot doesn't have leads, so this method does nothing.
*
* @param Carbon $since
* @param Carbon|null $to
* @param string|null $crmProfileId
*
* @return int
*/
public function syncLeads(Carbon $since, ?Carbon $to = null, ?string $crmProfileId = null): int
{
// Mark unused parameters to avoid code smell warnings
unset($since, $to, $crmProfileId);
return 0;
}
/**
* HubSpot doesn't have leads.
*
* @param string $crmId
*
* @inheritdoc
*/
public function syncLead(string $crmId): ?Lead
{
// Mark unused parameter to avoid code smell warnings
unset($crmId);
return null;
}
/**
* Sync accounts (companies) modified since a given date (manual sync mode).
*
* This method fetches companies from HubSpot API based on modification date and
* imports them one by one. It is used for:
* - Manual sync commands (e.g., crm:sync-account with --from parameter)
* - Initial sync for new teams
* - Backfill operations
*
* For regular sync webhook batchSyncCompanies is used:
*
* @param Carbon $since Fetch companies modified after this date
* @param Carbon|null $to Optional end date for modification range
*
* @return int Number of companies successfully synced
*/
public function syncAccounts(Carbon $since, ?Carbon $to = null): int
{
$syncCount = 0;
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$payload = $this->payloadBuilder->getRecentlyUpdatedSearchPayload($since, $to, $fields);
foreach ($this->client->getPaginatedDataGenerator($payload, 'companies') as $hsAccount) {
$this->importAccount($hsAccount);
$syncCount++;
}
} catch (Exception $exception) {
$this->logger->error('[' . $this->getDisplayName() . '] Sync accounts failed', [
'teamId' => $this->team->getUuid(),
'reason' => $exception->getMessage(),
]);
}
return $syncCount;
}
/**
* @inheritdoc
*/
public function syncAccount(string $crmId): ?Account
{
try {
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$fields = $this->getCompanyFields();
$hsAccount = $this->client->getAccountById($crmId, $fields);
} catch (\HubSpot\Client\Crm\Companies\ApiException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account fetch failed', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
} catch (CrmException $e) {
$this->logger->info('[' . $this->getDisplayName() . '] Account not found', [
'teamId' => $this->team->getUuid(),
'crmId' => $crmId,
'reason' => $e->getMessage(),
]);
return null;
}
return $this->importAccount($hsAccount);
}
/**
* Process webhook-collected contact batches.
*
* Drains Redis sets containing contact CRM IDs collected from webhook events
* and dispatches ImportContactBatch jobs for batch processing.
*
* @return int Number of contact IDs dispatched to jobs
*/
public function batchSyncContacts(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_CONTACT,
$configId
);
}
public function importContactBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowContacts = [];
$fetchStart = microtime(true);
$allContacts = $this->fetchContactsByIdsInChunks($crmIds);
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allContacts, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Contact CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allContacts),
]);
}
if (empty($allContacts)) {
return $result;
}
$prepareStart = microtime(true);
$accountMappings = $this->prepareAccountMappingsForContacts($allContacts);
$prepareAccountsMs = (int) round((microtime(true) - $prepareStart) * 1000);
$loopStart = microtime(true);
foreach ($allContacts as $contactData) {
$contactStart = microtime(true);
try {
$contact = $this->importContact($contactData, $accountMappings);
if ($contact !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $contactData['id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import contact', [
'teamId' => $this->team->getId(),
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$contactMs = (int) round((microtime(true) - $contactStart) * 1000);
if ($contactMs > 1000) {
$slowContacts[] = ['crmId' => $contactData['id'] ?? 'unknown', 'ms' => $contactMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importContactBatch timing', [
'teamId' => $this->team->getId(),
'contact_count' => \count($allContacts),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'prepare_accounts_ms' => $prepareAccountsMs,
'contacts_loop_ms' => $loopMs,
'avg_contact_ms' => ! empty($allContacts) ? (int) round($loopMs / \count($allContacts)) : 0,
'slow_contacts_count' => \count($slowContacts),
'slow_contacts' => array_slice($slowContacts, 0, 10),
]);
return $result;
}
private function fetchContactsByIdsInChunks(array $crmIds): array
{
$fields = $this->getContactFields();
$allContacts = [];
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$contacts = $this->client->getContactsByIds($chunk, $fields);
foreach ($contacts as $contactData) {
$allContacts[] = $contactData;
}
} catch (\Throwable $e) {
// @TODO what will happen if this exception is thrown
$this->logger->warning('[' . $this->getDisplayName() . '] Batch contact fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
return $allContacts;
}
private function prepareAccountMappingsForContacts(array $contacts): array
{
$companyIds = [];
foreach ($contacts as $contact) {
$companyId = $contact['properties']['associatedcompanyid'] ?? null;
if ($companyId !== null && $companyId !== '') {
$companyIds[] = (string) $companyId;
}
}
$companyIds = array_unique($companyIds);
if (empty($companyIds)) {
return [];
}
$mappings = $this->crmEntityRepository
->getExistingAccountIdsMap($this->config, $companyIds);
$missingCompanyIds = array_diff($companyIds, array_keys($mappings));
if (empty($missingCompanyIds)) {
return $mappings;
}
$this->logger->info('[' . $this->getDisplayName() . '] Batch syncing missing accounts for contacts', [
'teamId' => $this->team->getId(),
'total_companies' => \count($companyIds),
'existing_companies' => \count($mappings),
'missing_companies' => \count($missingCompanyIds),
]);
try {
$syncedAccounts = $this->batchSyncAccountsForContacts($missingCompanyIds);
$mappings = array_merge($mappings, $syncedAccounts);
} catch (\Throwable $e) {
$this->logger->error('[' . $this->getDisplayName() . '] Failed to batch sync missing accounts', [
'teamId' => $this->team->getId(),
'missingCompanyIds' => $missingCompanyIds,
'missingCount' => count($missingCompanyIds),
'error' => $e->getMessage(),
]);
}
return $mappings;
}
private function batchSyncAccountsForContacts(array $companyIds): array
{
$syncedAccounts = [];
$fields = $this->getCompanyFields();
foreach (array_chunk($companyIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
try {
$account = $this->importAccount($companyData);
if ($account) {
$syncedAccounts[$account->getCrmProviderId()] = $account->getId();
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account in batch', [
'teamId' => $this->team->getId(),
'companyId' => $companyData['id'] ?? 'unknown',
'error' => $e->getMessage(),
]);
}
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'teamId' => $this->team->getId(),
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
}
}
return $syncedAccounts;
}
/**
* Process webhook-collected company batches.
*
* Drains Redis sets containing company CRM IDs collected from webhook events
* and dispatches ImportAccountBatch jobs for batch processing.
*
* @return int Number of company IDs dispatched to jobs
*/
public function batchSyncCompanies(): int
{
$configId = $this->team->getCrmConfiguration()->getId();
return $this->batchProcessor->processBatchesForObjectType(
WebhookSyncBatchProcessor::OBJECT_TYPE_COMPANY,
$configId
);
}
public function importAccountBatchByIds(array $crmIds): array
{
$result = [
'success_count' => 0,
'failed_ids' => [],
'errors' => [],
];
if (! $this->client instanceof HubspotClientInterface) {
throw new \InvalidArgumentException('Client must implement HubspotClientInterface');
}
$batchStart = microtime(true);
$slowAccounts = [];
$fields = $this->getCompanyFields();
$allCompanies = [];
$fetchStart = microtime(true);
foreach (array_chunk($crmIds, self::BATCH_SIZE) as $chunk) {
try {
$companies = $this->client->getCompaniesByIds($chunk, $fields);
foreach ($companies as $companyData) {
$allCompanies[] = $companyData;
}
} catch (\Throwable $e) {
$this->logger->warning('[' . $this->getDisplayName() . '] Batch company fetch failed', [
'chunk_size' => \count($chunk),
'error' => $e->getMessage(),
]);
throw $e;
}
}
$fetchMs = (int) round((microtime(true) - $fetchStart) * 1000);
$fetchedIds = array_map('strval', array_column($allCompanies, 'id'));
$notFoundIds = array_values(array_diff(array_map('strval', $crmIds), $fetchedIds));
if (! empty($notFoundIds)) {
$this->logger->info('[' . $this->getDisplayName() . '] Company CRM IDs not found in HubSpot', [
'teamId' => $this->team->getId(),
'notFoundCount' => \count($notFoundIds),
'notFoundIds' => $notFoundIds,
'requestedCount' => \count($crmIds),
'fetchedCount' => \count($allCompanies),
]);
}
$loopStart = microtime(true);
foreach ($allCompanies as $companyData) {
$accountStart = microtime(true);
try {
$account = $this->importAccount($companyData);
if ($account !== null) {
$result['success_count']++;
}
} catch (\Throwable $e) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$result['failed_ids'][] = $crmId;
$result['errors'][$crmId] = $e->getMessage();
$this->logger->warning('[' . $this->getDisplayName() . '] Failed to import account', [
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
$accountMs = (int) round((microtime(true) - $accountStart) * 1000);
if ($accountMs > 1000) {
$crmId = $companyData['id'] ?? $companyData['properties']['hs_object_id'] ?? 'unknown';
$slowAccounts[] = ['crmId' => $crmId, 'ms' => $accountMs];
}
}
$loopMs = (int) round((microtime(true) - $loopStart) * 1000);
$totalMs = (int) round((microtime(true) - $batchStart) * 1000);
$this->logger->info('[' . $this->getDisplayName() . '] importAccountBatch timing', [
'teamId' => $this->team->getId(),
'account_count' => \count($allCompanies),
'requested_count' => \count($crmIds),
'not_found_count' => \count($notFoundIds),
'total_ms' => $totalMs,
'fetch_api_ms' => $fetchMs,
'accounts_loop_ms' => $loopMs,
'avg_account_ms' => ! empty($allCompanies) ? (int) round($loopMs / \count($allCompanies)) : 0,
'slow_accounts_count' => \count($slowAccounts),
'slow_accounts' => array_slice($slowAccounts, 0, 10),
]);
return $result;
}
private function getCompanyFields(): array
{
return [
'country',
'name',
'phone',
'domain',
'hs_avatar_filemanager_key',
'industry',
'hubspot_owner_id',
'hs_object_id',
'createdate',
'hs_lastmodifieddate',
];
}
private function importAccount($crmData): ?Account
{
$crmProviderId = $crmData['id'] ?? $crmData['properties']['hs_object_id'] ?? null;
$this->logger->info('[HubSpot] importAccount', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
if (empty($crmData['properties'])) {
$this->logger->info('[HubSpot] importAccount failed: empty properties', [
'crm_provider_id' => $crmProviderId,
'config_id' => $this->config->getId(),
]);
return null;
}
$properties = $crmData['properties'];
$crmId = (string) $properties['hs_object_id'];
$countryCode = isset($properties['country']) ? $this->convertCountryNameToCode($properties['country']) : null;
if (isset($properties['phone'])) {
// Trim to our width and attempt to parse it.
$number = mb_strimwidth($properties['phone'], 0, 25);
$parsedNumber = parsePhoneNumber($countryCode, $number);
} else {
$parsedNumber = [];
}
$name = '[unknown]';
if (isset($properties['name'])) {
$name = $properties['name'];
}
$photoPath = $this->prospectPhotoPathService->getOrGeneratePhotoPath(
$this->config,
$crmId,
Account::class,
$crmId,
$name
);
$industry = null;
if (isset($properties['industry'])) {
$industry = mb_strimwidth($properties['industry'], 0, 40);
}
$ownerId = $profile = null;
if (isset($properties['hubspot_owner_id'])) {
$ownerId = $properties['hubspot_owner_id'];
$profile = $this->crmEntityRepository->findProfileByExternalId($this->config, (string) $ownerId);
}
$domain = null;
if (isset($properties['domain'])) {
$domain = StringUtil::resolveDomain($properties['domain']);
}
$remotelyCreatedAt = null;
if (isset($properties['createdate']) && ! empty($properties['createdate'])) {
$remotelyCreatedAt = Carbon::parse($properties['createdate']);
}
$data = [
'crm_provider_id' => $crmId,
'team_id' => $this->team->id,
'user_id' => $profile ? $profile->user_id : null,
'owner_id' => $ownerId,
'name' => mb_strimwidth($name, 0, 191),
'photo_path' => $photoPath,
'industry' => $industry,
'domain' => $domain !== null
? substr($domain, 0, 191)
: null,
'phone' => $parsedNumber['phone'] ?? null,
'ext' => $parsedNumber['ext'] ?? null,
'country_code' => $countryCode,
'remotely_created_at' => $remotelyCreatedAt,
];
return $this->crmEntityRepository->importAccount($this->config, $data);
}
public function deleteContact(string $crmProviderId): bool
{
try {
$contact = $this->crmEntityRepository->findContactByExternalId($this->config, $crmProviderId);
if (! $contact) {
$this->logger->info('[HubSpot] Contact not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $contact->getId();
$this->logger->info('[HubSpot] Deleting contact via webhook', [
'contact_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$contact->delete();
DeleteContactJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete contact via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteAccount(string $crmProviderId): bool
{
try {
$account = $this->crmEntityRepository->findAccountByExternalId($this->config, $crmProviderId);
if (! $account) {
$this->logger->info('[HubSpot] Account not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $account->getId();
$this->logger->info('[HubSpot] Deleting account via webhook', [
'account_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$account->delete();
DeleteAccountJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete account via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
public function deleteOpportunity(string $crmProviderId): bool
{
try {
$opportunity = $this->crmEntityRepository->findOpportunityByExternalId($this->config, $crmProviderId);
if (! $opportunity) {
$this->logger->info('[HubSpot] Opportunity not found for deletion', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
// return success because we do not know which instance is the target
return true;
}
$id = $opportunity->getId();
$this->logger->info('[HubSpot] Deleting opportunity via webhook', [
'opportunity_id' => $id,
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
]);
$opportunity->delete();
DeleteOpportunityJob::dispatch($id)->afterCommit();
return true;
} catch (Exception $e) {
$this->logger->error('[HubSpot] Failed to delete opportunity via webhook', [
'crm_provider_id' => $crmProviderId,
'team_id' => $this->team->getId(),
'error' => $e->getMessage(),
]);
return false;
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app, folder
.circleci, folder
.cursor, folder
.github
.sonarlint, folder
.vscode, folder
.windsurf, folder
app, sources root
Actions, folder
Component, folder
Acl, folder
ActionItems, folder
Activity, folder
ActivityAnalytics, folder
ActivitySearch, folder
AiActivityType, folder
AiAutomation, folder
AiCallScoring, folder
AskAnything, folder
Dtos, folder
Events, folder
AskAnythingPromptService.php
HistoryService.php
AskJiminnyAi, folder
AWS, folder
BillingManagement, folder
Cache, folder
CoachingFeedback, folder
Country, folder
CustomerApi, folder
Database, folder
Datadog, folder
DateTime, folder
DealInsights, folder
DealRisks, folder
ElasticSearch
Eloquent
Encoding
Encryption
ES
Faker
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
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
Job, folder
RateLimitAware.php, abstract class
RateLimitAwareWrapper.php, class
BotsQueueConstants.php
Constants.php
ProcessingQueueConstants.php
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
Exceptions, folder
Service, folder
BaseRateLimiter.php, class
EfficientJsonParser.php, class
ProviderRateLimiter.php, class
RateLimiterInstance.php, class
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration, folder
Console, folder
Commands, folder
Activities, folder
Analytics, folder
Calendars, folder
Crm, folder
Hubspot, folder
IntegrationApp, folder
Traits, folder
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php, class
BullhornSessionCommand.php, class
CheckActivityLoggableCommand.php, final class
CleanDuplicateFieldDataCommand.php, class
FullSyncOpportunityCommand.php, class
LogActivitiesCommand.php, final class
ManageSyncStrategyCommand.php, class
MatchCrmObjectsCommand.php, class
MatchOpportunityActivitiesCommand.php, class
MigrateProvider.php, class
ProcessHubspotObjectsSyncBatches.php, class
PurgeDeletedOpportunitiesCommand.php, class
ResetGovernorLimits.php, class
SendNotLogged.php, class
SetupActivityTypeForFollowUp.php, final class
SetupCloseCrm.php, class
SetupCopperCrm.php, class
SetupCrmCommand.php, abstract class
SetupLayouts.php, class
SyncAccount.php, class
SyncContact.php, class
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
AddRateLimitCommand.php, class
FixHubSpotTokens.php, class
FixMissMatchedCrmActivitiesCommand.php, final class
ImportCallsCommand.php, class
MonitorSocialAccountsState.php, class
Dialers, folder
DTOs, folder
Elasticsearch, folder
EngagementStats, folder
GeckoExport, folder
Livestream, folder
Mailboxes, folder
Migrate, folder
PlaybackThemes, folder
Playbooks, folder
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php, class
AutomatedReportsRetentionPolicyCommand.php, class
AutomatedReportsSendCommand.php, class
CreateMockAskJiminnyReportResultCommand.php, class
DeleteReportCommand.php, class
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack, folder
Teams, folder
Tracks, folder
Transcription, folder
Twilio, folder
Users, folder
Vocabulary, folder
Zoom, folder
Command.php
CreateDatabaseUsers.php
DatabaseTableCount.php
DeleteOldAiCrmNotesCommand.php
DeleteS3LeftoversCommand.php
DevPostmanCommand.php
DiarizeViaAiParticipantIdentificationCommand.php
EncryptTokensCommand.php
EngagementStatsRegenerateCommand.php
FeatureFlagsHelper.php
FixCrossTenantIssues.php
FlushRolesPermissionsCache.php
GenerateInternalWebhookToken.php
GroupSetDefaultLanguageCommand.php
HelperTruncateCoachingTables.php
HubspotJournalPollingCommand.php
HubspotWebhookServiceCommand.php
ImportRecording.php
ImportUsersFromCsvFile.php
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php
JiminnySetEncryptedTokenManagerModeCommand.php
JiminnyTokenInfoCommand.php
MakeSlackLiveCoachingChatNotesOn.php
ManageScimForTeam.php
MarkBranchForEnvironmentPipelineCommand.php
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php
RemoveExpiredNudgesCommand.php
RemoveUnusedParticipantSpeechesCommand.php
ResetElasticSearch.php
RestoreActivityCrmProviderIdCommand.php
RestoreActivityTypeCommand.php
RunAiCallScoringForUntypedActivitiesCommand.php
SeedActivities.php
SendNudgeExpirationWarningsCommand.php
SyncActivity.php, class...
|
2842
|
NULL
|
NULL
|
NULL
|
|
2846
|
114
|
37
|
2026-05-07T11:43:07.424091+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-07/1778 /Users/lukas/.screenpipe/data/data/2026-05-07/1778154187424_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmEntitiesTrait.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
Editor for custom.log...
|
[{"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.034242023,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: master","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":"AXTextArea","text":"Editor for custom.log","depth":4,"bounds":{"left":0.5475399,"top":0.0726257,"width":0.44082448,"height":0.9066241},"on_screen":true,"role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
61097548596911936
|
-8132287983061464126
|
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
Editor for custom.log
PhostormINavigarecodeLaravelKeractorFV faVsco.jsProiect vRematchActivityOnCrmObjectDetach.phpT DeleteCrmEntityTrait.phpv D ServiceTraitsT OpportunitySync1© RateLimitException.phpAddkateLimitcommand.php© Hubspot/Client.php© SyncOpportunity.php© WebhookSyncBatchProcessor.php© ImportOpportunityBatch.phpu syncermentites iu syncrielasiralt.olHip/RateLimited.php© BaseRateLimiter.php© Service.phpT SyncCrmEntitiesTrait.php XT WriteCrmTrait.ph•DUuis> D Webhook© OpportunitySyncTest.php© RateLimiterInstance.php© ProviderRateLimiter.phptrait synccrmentitzestraitM| A62 X32 A Vc) Batchsynccollector.© BatchSvncRedisServC) Client.php(c) ClosedDealStagesSeDealFieldsService.ph@ DecorateActivitv.ohrcFieldDerinitions.onvC) FieldivoeConverter.n1) HubsootClientinterfa(C) HubsootTokenMana‹71 6f© PayloadBuilder.php@ PemoteCrmObiectM:YC) RocnonseNormalize 1© Service.php© SyncFieldAction.php 100 ai ©© SyncRelatedActivity^ 101© WebhookSyncBatchF 102• D IntegrationApp› D Accessorscontio> DDTO• W Filters> D Jobs• C ProspectSearchStrat 110)›D ServiceTraitsc) Dataclient.phpc) DecorateActivtv.ono 115C)LocalSearch.ohv1) LocalSearchinterface 115)(C) RemoteSearch.ono(C) Service,ohov m ListenersC) Convertl eadActivitie 119C) Purael.ookuncache.r 120> M Metadata• M Miaration>D Pipedrive• M Salesforce> M Siolde>@ OpportunityMatcher› D OpportunitySyncStra 127M DrocnontCaarchCtrat 129• M CorvicoTraite* - Backfill operations* ror regular sync webnook bacchsyncconcacts is used.* doaram carbon sSince reuch concacus moulried arcer chis date: Qparam CarbonInulz Sto Optional end date for modification rangepublic function svncContacts(Carbon Ssince. ?Carbon Sto = null): int{...}* Ginheritdodnublic function svncContact(strina Scrmid: ?Contacttryif (! Sthis->client instanceof HubspotClientInterface) i+hnow new TnvalidAraumen+Sycentiond medage: 'Client must implement HubspotClientInterface');$fields = $this->getContactFields:$hsContact = Sthis->client->getContactById($crmId, $fields):} catch (\HubSpot\Client\Crm\Contacts'Chanco NodlaratinnSthis->logger->info('[' . $this->gm d getContactByld (HubspotClientinterface .../app/Services/Crm/Hubspo"ceamld" = schls->rean->geruu'crmId' => $crmId'reason' => $e->qetMessageOO e getcontactiyld (cLient .../app/Servzces/crm/Hubspot)1);} catch (CrmException Se) {Sthis->loqger->1nfo('|'• Sthis->qetd1solavNameo.'] Contact not found', ["teamid => sthus->team->cetuuido'reason' => Se->aetMessageOl14iemntv(ChstontactiinronentiectilemntvShctontactttidr1nncSthis-sloagen-swanning(trt Sthis->aethisnlavNameo."Contact data inconnletel."teamtd' => Sthis->team->aetlluidoarAube for IDE suanestionsa Detect more seawritvlssues lin vour D.Dffiles lltn SonarAube Claud for free //lDownload SonarOmbe Server /llllear more /llDonit ask adain /itodav 10525111 1111= custom.log X = laravel.log4 SF jjiminny@localhost]HS_local (jiminny@localhost]# console [PKol)A console (eu)S0 lho# Support Daily - in 17 mU AskJiminnyReportActivityServiceTest v« console [STAGING]100% CThu 7 May 14:43:07WN Windsurf Teamc108:47 UTF-8 # 4 spaces...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65473
|
2306
|
17
|
2026-05-21T07:40:59.611096+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349259611_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
6767534694907463242
|
-8141198994779927276
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65474
|
2306
|
18
|
2026-05-21T07:41:00.437323+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349260437_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
-7862546749473264626
|
-8142254525942592236
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class...
|
65473
|
NULL
|
NULL
|
NULL
|
|
65475
|
2307
|
13
|
2026-05-21T07:41:00.421514+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349260421_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, 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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.0,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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"}]...
|
-661905251928204165
|
-8717730116901184236
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65476
|
2306
|
19
|
2026-05-21T07:41:02.400573+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349262400_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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"}]...
|
-8555780578124376235
|
-8142395263430931180
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65477
|
2307
|
14
|
2026-05-21T07:41:02.384353+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349262384_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.0071827616,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"}]...
|
5310908547720353950
|
-8717730081467720428
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk...
|
65475
|
NULL
|
NULL
|
NULL
|
|
65478
|
2306
|
20
|
2026-05-21T07:41:03.176791+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349263176_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"}]...
|
-2857235976388263470
|
-8717730116901184236
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot...
|
65476
|
NULL
|
NULL
|
NULL
|
|
65479
|
2307
|
15
|
2026-05-21T07:41:03.176790+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349263176_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.0071827616,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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"}]...
|
5572889657176821371
|
-6411746335839417068
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65480
|
2306
|
21
|
2026-05-21T07:41:04.712459+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349264712_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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"}]...
|
-1566601011576084229
|
-8717730117438055148
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65481
|
2307
|
16
|
2026-05-21T07:41:04.693708+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349264693_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.19780585,"top":0.007980846,"width":0.25897607,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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"}]...
|
7089743142087417934
|
-8717591578436149996
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
65479
|
NULL
|
NULL
|
NULL
|
|
65482
|
2306
|
22
|
2026-05-21T07:41:05.439497+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349265439_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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"}]...
|
-3183846560324211647
|
-8717730116901200620
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder...
|
65480
|
NULL
|
NULL
|
NULL
|
|
65483
|
2307
|
17
|
2026-05-21T07:41:05.419501+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349265419_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.19780585,"top":0.007980846,"width":0.25897607,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"}]...
|
5817002037882781695
|
-8717730082541445868
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65484
|
2306
|
23
|
2026-05-21T07:41:35.787977+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349295787_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
8460510130546683954
|
-6412872269032321772
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65485
|
2307
|
18
|
2026-05-21T07:41:35.862833+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349295862_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"on_screen":false,"role_description":"text"}]...
|
-4969646139324939661
|
-6409495119138910956
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class...
|
65483
|
NULL
|
NULL
|
NULL
|
|
65486
|
2306
|
24
|
2026-05-21T07:41:53.210434+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349313210_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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...
|
[{"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/>74 incoming commits<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
SlackFileEditViewGoHistoryWindowHelpscreelDOCKER0 81DEV (-zsh)₴2APP (-zsh)whisper_init_state:whisper_init_state:kv padsize=2.36 MBcompute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)=whisper_init_state:compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocatingwhisper_backend_init_gpu: device 0: Metal (type:1)whisper_backend_init_gpu: found GPU device 0: Metal (type: 1, cnt: 0)whisper_backend_init_gpu: using Metal backendggml_metal_init: allocatingggml_metal_init: found device: Apple M1ggml_metal_init: picking default device: Apple M1ggml_metal_init:use fusion= trueggml_metal_init:use concurrency= trueggml_metal_init: use graph optimizetruewhisper_backend_init: using BLAS backendwhisper_init_state: kv self size3.15 MBwhisper_init_state: kv cross size =9.44 MBwhisper_init_state: kv padsize =2.36 MBwhisper_init_state: compute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)=whisper_init_state: compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocatingwhisper_backend_init_gpu:device 0: Metal (type: 1)whisper_backend_init_gpu: found GPUdevice 0: Metal (type: 1, cnt: 0)whisper_backend_init_gpu: using Metal backendggml_metal_init: allocatingggml_metal_init: found device: Apple M1ggml_metal_init: picking default device: Apple M1ggml_metal_init:use fusion= trueggml_metal_init:use concurrencytrueggml_metal_init:use graph optimizetruewhisper_backend_init: using BLAS backendwhisper_init_state: kv self size3.15 MBwhisper_init_state: kv cross size =9.44 MBwhisper_init_state: kv padsize=2.36 MBwhisper_init_state: compute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)whisper_init_state: compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocating2026-05-21T10:13:41.913005ZINFO screenpipe_audio::audio_manager::manager: reconciliatior2026-05-21710:15:15.5768762INFO screenpipe_engine::event_driven_capture: content dedup:j Support Daily - in 4h 45 m100% С8• Thu 21 May 10:15:32•••EDQHomeDMsActivityFilesLater•••More+→Describe what you are looking forJiminny ...ThreadsC9 Unreadsà platform-inner-teamGalya Dimitrova, Nikolay Yankov, and youa ThreadsHuddles• Drafts & sentDirectories01External connections* Starred& jiminny-x-integrati...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity_lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# supportLukas Kovalik 31 minutes agoдобро утро, тази страница не трябва ли да япоказваме само ако няма reportsCleanShot 2026-05-21 at [EMAIL] your questions into ongoing insightsGalya Dimitrova 23 minutes agoДа, би трябвалоLukas Kovalik 22 minutes agoче сега ми го показва и ако имам reportLukas Kovalik 21 minutes agoна neptuneNewNikolay Yankov 6 minutes agoвсичко е наредReply...Also send to à platform-inner-team+AaYou're up to date...
|
65484
|
NULL
|
NULL
|
NULL
|
|
65488
|
2307
|
19
|
2026-05-21T07:41:53.210431+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349313210_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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...
|
[{"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/>74 incoming commits<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
|
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
slackFV [EMAIL]=.phounit.result.cacheE .prettierignore= windsurtrulesphpde neloer.onophpide heloer_models.ohophp artisanM'CLAUDE.mdcomooser.isoncomooser.lockdependency-checker.json8 dev ison= ids tytl=infection.ison.distMLINSTAlI.mdMLINTERNAI WERHOOK SFTUP mdEjiminny_storageMiliconsos mdllM Makefile1рackace-lock. sonE phpstan.neon.dist= phostan-baseline.neon<> phpunit.xmlTe raw sal query.saM+ README.mdsô sonar-proiect propertiesE test.ov‹> Untitled Diaaram.xmliusvetur.confio.isM+ WEBHOOK FILTERING_IMPLEMENTATION.mo>1h External Librariesv =° Scratches and Consolesv batabase Consolesv AEU# console (EUlI DEAL RISKS (SU1A DITEUlA EU (EU]v Ajiminny@localhostA console (jiminny@localhost]# Di [liminny@localhostlA HS local [iiminny@localhostlA SF ['iiminny@localhost]& zoho dev [iiminny@localhost]V & PRODA console [PROD]& console 1 [PROD1LDIIPRODCActivityController.ong© ConvertLeadActivities.php= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console (PROD] X# console [euyLeadkepository.ong© PurgeLookupCache.php© SyncPlanhat.phpo synerorlannatpnpA console [STAGING]AcuivityLeadConverted.php© UserAutomatedreporscontroller.phpx© PlaybackController.php)Inteqrationapp/service.onoC) LeadConverted.ono570571C) CreateCommentedEvent.php(C) CreateSmsSentEvent.phpC) PlanhatActivityListener.php© AskAnythingPromptService.php572(C) AutomatedReportsRepositorv.php© AutomatedReportsCommand.phpphp api_v2.phpC) RequestGenerateReportJob.php573574© AutomatedReportResult.php(C) AutomatedReport.oho575use Jiminny \Services \Kiosk \AutomatedReports \ReportSortDirection;A1422 ^ v 576use Jiminny \Services\PlanhatService;use ectumtnace hutp kequestuse Inrowable,class UserAutomatedReportsController extends Controllenpublic const int RESULTS PER_ PAGE = 25:public const string SORT COLUMN = 'sort column':public const strina SORT DIRECTION = "sort direction':public function constructprivate readonly AutomatedReportsRepository $automatedReportsRepository.private readonly AutomatedRenortsService SautomatedRenortsServicelorivate readoni.v AniResponseService samiResponseServi.ce.orivate readoni.v Resnonse Sresnonse.private readonly PlanhatService $planhatService1...poST lanilv1lautomated-renorts/interest 1usadepublic function trackInterest(Request $request): JsonResponse(...}* Othnows ApplicationException58(09 >GET /api/v1/automated-reportspublic function list(Request Srequest): JsonResponse(...}125 0)DELETE/api/vl/automated-reports/kuuidpublic function delete(Request Srequest. string Suuid): JsonResponsef...} -609610--611612613616Tx: AutovSo jiminny vCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE'' END) AS USEr_i041 A1 A40 V65 ^sa.*t.owner_id FROM social_accounts saNoun usens u on u.ide sa.sociablle 1diJOIN 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-7345381b6044|"pat", "podcast"SELECT * FROM automated report results WHERE uuid to bin( '822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMWHERE id = 1919:select * fromautomated report results WHERE report id = 54:select * from opportunities where id = 7594349:SELECT * FROM teams WHERE name LIKE '%Lesg': # 711. 692. [EMAIL] * from playbooks where team_id = 711; # event 226147SELEd * FROM Dlavbook catedonies WHERE Dlavbook 1d = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event':SELECT * SROM eom fields WHEREI1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;SELSCTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '• END) AS user_id,v.emailsa.*,t.owner id FROM social accounts saJOIN users u on v.id = sa.sociable idJOIN teams t 1..n<->1: on t.id = u.team idWHERE U.team_id = 711 and sa.provider = 'salesforce";SELECT * FROM crm profiles cp JOIN users u 1..n<->1: on u.id = co.user id WHERE u.team_id = 711:select * from leadsselect * from calendarsSELECTS 0 hllsupoont Dally • In 4h 40m100% 52• Inu z1 May 10.10.34AskJiminnyReportActivityServiceTestvCascadePlaybook Categorie+0 ..connected. How isi. Can we vali eoneactivity tyee nith orit cabest aeeiewmane o, W dati anwsyggesinsingrow hohow to distinguishThoughts68 Fast Context How are playbook_categories populated and synced from crm_fields / crm_field_values... in 4.14s >Explored 4 files and searched 5 queriesRead ImportActivitvTypes.php #L95-104Searched olavbook cateaories in databaselmiarations (4) >egories|playbook_categories.*create in database/migrations (0)searched playoook_categor in database/migrations (<)Searched activity type in database/migrations (4)SurtingAsk anvthina (*4L)@ Codo Caudo Onue 17 ModiumNN Windeurf Toame 504.60...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65497
|
2306
|
30
|
2026-05-21T07:42:19.478269+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349339478_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"}]...
|
5177837308458605725
|
-8717730082541445868
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags...
|
65495
|
NULL
|
NULL
|
NULL
|
|
65498
|
2307
|
24
|
2026-05-21T07:42:19.478300+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349339478_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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"}]...
|
-6432662851748720335
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide
app ~/jiminny/app
.circleci
.cursor
.github
.sonarlint
.vscode
.windsurf...
|
65496
|
NULL
|
NULL
|
NULL
|
|
65499
|
NULL
|
0
|
2026-05-21T07:42:49.824470+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349369824_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
4668560580297988277
|
-8715337578525488876
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65500
|
NULL
|
0
|
2026-05-21T07:42:49.907216+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349369907_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, 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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"}]...
|
-2571589278577124073
|
-8717589928094986988
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65501
|
2308
|
0
|
2026-05-21T07:43:20.152555+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349400152_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
-4331963242933399359
|
-8715337578525487852
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class...
|
65499
|
NULL
|
NULL
|
NULL
|
|
65502
|
2309
|
0
|
2026-05-21T07:43:20.284617+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349400284_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php, class","depth":10,"on_screen":false,"role_description":"text"}]...
|
-5269841006065165550
|
-6409495119067591404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class...
|
65500
|
NULL
|
NULL
|
NULL
|
|
65503
|
2308
|
1
|
2026-05-21T07:43:50.471601+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349430471_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"}]...
|
-4331963242933399359
|
-8715337578525487852
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65504
|
2309
|
1
|
2026-05-21T07:43:50.657371+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349430657_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false}]...
|
8830251352522217365
|
-8708584105377178620
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65505
|
2308
|
2
|
2026-05-21T07:44:20.853701+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349460853_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"}]...
|
-9223056471733185465
|
-8717589928094988012
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes...
|
65503
|
NULL
|
NULL
|
NULL
|
|
65506
|
2309
|
2
|
2026-05-21T07:44:21.034497+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349461034_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IssueMcpTokenCommand.php, class
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php, class
RemoveExpiredNudgesCommand.php, class
RemoveUnusedParticipantSpeechesCommand.php, class
ResetElasticSearch.php, class
RestoreActivityCrmProviderIdCommand.php, class
RestoreActivityTypeCommand.php, class
RunAiCallScoringForUntypedActivitiesCommand.php
SeedActivities.php
SendNudgeExpirationWarningsCommand.php
SyncActivity.php
TrackImported.php
WhichWorkerIsWorkingOnWhichJob.php
Scheduling, 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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlags","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FFMpeg","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FileSystem","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gecko","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Gong","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GuzzleHttp","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"KeyPoints","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Kiosk","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LanguageDetection","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LiveFeed","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Locks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Math","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MediaPipeline","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MeetingBot","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MobileSettings","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","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlist","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Prophet","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProsperWorks","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Queue","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Router","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Saml2","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SCIM","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Seeder","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sentry","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Serializer","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Settings","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Sidekick","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TeamInsights","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TimeMemoryMapper","depth":9,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","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","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Analytics","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Calendars","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Crm","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Hubspot","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IntegrationApp","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Traits","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AddLayoutEntities.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutologDelayedCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BackfillOpportunityUserFromAccountCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornCommandAbstract.php, abstract class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornPingCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSearchCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"BullhornSessionCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CheckActivityLoggableCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CleanDuplicateFieldDataCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FullSyncOpportunityCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"LogActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageSyncStrategyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchCrmObjectsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MatchOpportunityActivitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MigrateProvider.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProcessHubspotObjectsSyncBatches.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeDeletedOpportunitiesCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetGovernorLimits.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNotLogged.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupActivityTypeForFollowUp.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCloseCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCopperCrm.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupCrmCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SetupLayouts.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncAccount.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncContact.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncFieldMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotActiveDeals.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncHubspotObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncLead.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncObjects.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunitiesMissingFieldDataCommand.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncOpportunity.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncProfileMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncTeamMetadata.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"UpdateOpportunitySpecifications.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DealInsights, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dev, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Dialers, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DTOs","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Elasticsearch","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStats","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GeckoExport","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Livestream","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Mailboxes, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Migrate","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PlaybackThemes","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playbooks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Playlists, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Postmark, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ProphetAi, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Reports, folder","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsRetentionPolicyCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"AutomatedReportsSendCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateMockAskJiminnyReportResultCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteReportCommand.php","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateMarketingReport.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Team.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Usage.php, class","depth":11,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Slack","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Teams","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Tracks","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Transcription","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Twilio","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Users","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Vocabulary","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Zoom","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Command.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"CreateDatabaseUsers.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DatabaseTableCount.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteOldAiCrmNotesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DeleteS3LeftoversCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DevPostmanCommand.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"DiarizeViaAiParticipantIdentificationCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EncryptTokensCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"EngagementStatsRegenerateCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FeatureFlagsHelper.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FixCrossTenantIssues.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"FlushRolesPermissionsCache.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GenerateInternalWebhookToken.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"GroupSetDefaultLanguageCommand.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HelperTruncateCoachingTables.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotJournalPollingCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"HubspotWebhookServiceCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportRecording.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ImportUsersFromCsvFile.php, final class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IssueMcpTokenCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"IterateUsersCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyCacheClearCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyDebugCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnySetEncryptedTokenManagerModeCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"JiminnyTokenInfoCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MakeSlackLiveCoachingChatNotesOn.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ManageScimForTeam.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MarkBranchForEnvironmentPipelineCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"MuteOrganizerChannel.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PhpApm.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeConferences.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSoftDeletedOpportunitiesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"PurgeSyncBatchesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RecalculateDealRisksCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveDeleteMarkersCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveExpiredNudgesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RemoveUnusedParticipantSpeechesCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"ResetElasticSearch.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityCrmProviderIdCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RestoreActivityTypeCommand.php, class","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"RunAiCallScoringForUntypedActivitiesCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SeedActivities.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SendNudgeExpirationWarningsCommand.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"SyncActivity.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"TrackImported.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"WhichWorkerIsWorkingOnWhichJob.php","depth":10,"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"Scheduling, folder","depth":9,"on_screen":false,"role_description":"text"}]...
|
4125655130040039617
|
-6409497318090846956
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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
FeatureFlags
FFMpeg
FileSystem
Gecko
Gong
GuzzleHttp
KeyPoints
Kiosk
LanguageDetection
LiveFeed
Locks
Math
MediaPipeline
MeetingBot
MobileSettings
Model, folder
Notification, folder
Nudge, folder
ParagraphBreaker, folder
ParticipantSpeech, folder
PartitionedCookie, folder
PlaybackPage
Playlist
Prophet
ProphetAi
ProsperWorks
Queue
Router
Saml2
SCIM
Seeder
Sentry
Serializer
Settings
Sidekick
Slack
TeamInsights
TimeMemoryMapper
Transcription
TranscriptionSummary, folder
Twilio, folder
Uploader, folder
UrlGenerator, folder
Utility, folder
Uuid, folder
Waveform, folder
Webhooks, folder
Workflow, folder
Configuration
Console
Commands, folder
Activities
Analytics
Calendars
Crm
Hubspot
IntegrationApp
Traits
AddLayoutEntities.php, class
AutologDelayedCommand.php, class
BackfillOpportunityUserFromAccountCommand.php, class
BullhornCommandAbstract.php, abstract class
BullhornPingCommand.php, class
BullhornSearchCommand.php
BullhornSessionCommand.php
CheckActivityLoggableCommand.php
CleanDuplicateFieldDataCommand.php
FullSyncOpportunityCommand.php
LogActivitiesCommand.php
ManageSyncStrategyCommand.php
MatchCrmObjectsCommand.php
MatchOpportunityActivitiesCommand.php
MigrateProvider.php
ProcessHubspotObjectsSyncBatches.php
PurgeDeletedOpportunitiesCommand.php
ResetGovernorLimits.php
SendNotLogged.php
SetupActivityTypeForFollowUp.php
SetupCloseCrm.php
SetupCopperCrm.php
SetupCrmCommand.php
SetupLayouts.php
SyncAccount.php
SyncContact.php
SyncFieldMetadata.php, class
SyncHubspotActiveDeals.php, class
SyncHubspotObjects.php, class
SyncLead.php, class
SyncObjects.php, class
SyncOpportunitiesMissingFieldDataCommand.php, class
SyncOpportunity.php, class
SyncProfileMetadata.php, class
SyncTeamMetadata.php, class
UpdateOpportunitySpecifications.php, class
DealInsights, folder
Dev, folder
Dialers, folder
DTOs
Elasticsearch
EngagementStats
GeckoExport
Livestream
Mailboxes, folder
Migrate
PlaybackThemes
Playbooks
Playlists, folder
Postmark, folder
ProphetAi, folder
Reports, folder
AutomatedReportsCommand.php
AutomatedReportsRetentionPolicyCommand.php
AutomatedReportsSendCommand.php
CreateMockAskJiminnyReportResultCommand.php
DeleteReportCommand.php
GenerateMarketingReport.php, class
Team.php, class
Usage.php, class
Slack
Teams
Tracks
Transcription
Twilio
Users
Vocabulary
Zoom
Command.php, class
CreateDatabaseUsers.php, class
DatabaseTableCount.php, class
DeleteOldAiCrmNotesCommand.php, class
DeleteS3LeftoversCommand.php, class
DevPostmanCommand.php, final class
DiarizeViaAiParticipantIdentificationCommand.php, class
EncryptTokensCommand.php, class
EngagementStatsRegenerateCommand.php, class
FeatureFlagsHelper.php
FixCrossTenantIssues.php, class
FlushRolesPermissionsCache.php, class
GenerateInternalWebhookToken.php, class
GroupSetDefaultLanguageCommand.php, final class
HelperTruncateCoachingTables.php, class
HubspotJournalPollingCommand.php, class
HubspotWebhookServiceCommand.php, class
ImportRecording.php, class
ImportUsersFromCsvFile.php, final class
IssueMcpTokenCommand.php, class
IterateUsersCommand.php
JiminnyCacheClearCommand.php
JiminnyDebugCommand.php, class
JiminnySetEncryptedTokenManagerModeCommand.php, class
JiminnyTokenInfoCommand.php, class
MakeSlackLiveCoachingChatNotesOn.php, class
ManageScimForTeam.php, class
MarkBranchForEnvironmentPipelineCommand.php, class
MuteOrganizerChannel.php, class
PhpApm.php, class
PropagateCoachingFeedbackCreatedAtToSectionFeedbacks.php, class
PurgeConferences.php, class
PurgeSoftDeletedOpportunitiesCommand.php, class
PurgeSyncBatchesCommand.php, class
RecalculateDealRisksCommand.php, class
RemoveDeleteMarkersCommand.php, class
RemoveExpiredNudgesCommand.php, class
RemoveUnusedParticipantSpeechesCommand.php, class
ResetElasticSearch.php, class
RestoreActivityCrmProviderIdCommand.php, class
RestoreActivityTypeCommand.php, class
RunAiCallScoringForUntypedActivitiesCommand.php
SeedActivities.php
SendNudgeExpirationWarningsCommand.php
SyncActivity.php
TrackImported.php
WhichWorkerIsWorkingOnWhichJob.php
Scheduling, folder...
|
65504
|
NULL
|
NULL
|
NULL
|
|
65507
|
2308
|
3
|
2026-05-21T07:44:36.323661+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349476323_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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},{"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"}]...
|
-6151875786856534370
|
-8717591544076362732
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65508
|
2309
|
3
|
2026-05-21T07:44:36.296703+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349476296_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…...
|
[{"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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
-1052979189915093189
|
-8141130793920488428
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65527
|
2310
|
1
|
2026-05-21T07:48:32.293318+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349712293_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpscreelDOCKER0 SlackFileEditViewGoHistoryWindowHelpscreelDOCKER0 81DEV (-zsh)₴2APP (-zsh)whisper_init_state:whisper_init_state:kv padsize=2.36 MBcompute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)=whisper_init_state:compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocatingwhisper_backend_init_gpu: device 0: Metal (type:1)whisper_backend_init_gpu: found GPU device 0: Metal (type: 1, cnt: 0)whisper_backend_init_gpu: using Metal backendggml_metal_init: allocatingggml_metal_init: found device: Apple M1ggml_metal_init: picking default device: Apple M1ggml_metal_init:use fusion= trueggml_metal_init:use concurrency= trueggml_metal_init: use graph optimizetruewhisper_backend_init: using BLAS backendwhisper_init_state: kv self size3.15 MBwhisper_init_state: kv cross size =9.44 MBwhisper_init_state: kv padsize =2.36 MBwhisper_init_state: compute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)=whisper_init_state: compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocatingwhisper_backend_init_gpu:device 0: Metal (type: 1)whisper_backend_init_gpu: found GPUdevice 0: Metal (type: 1, cnt: 0)whisper_backend_init_gpu: using Metal backendggml_metal_init: allocatingggml_metal_init: found device: Apple M1ggml_metal_init: picking default device: Apple M1ggml_metal_init:use fusion= trueggml_metal_init:use concurrencytrueggml_metal_init:use graph optimizetruewhisper_backend_init: using BLAS backendwhisper_init_state: kv self size3.15 MBwhisper_init_state: kv cross size =9.44 MBwhisper_init_state: kv padsize=2.36 MBwhisper_init_state: compute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)whisper_init_state: compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocating2026-05-21T10:13:41.913005ZINFO screenpipe_audio::audio_manager::manager: reconciliatior2026-05-21710:15:15.5768762INFO screenpipe_engine::event_driven_capture: content dedup:j Support Daily - in 4h 45 m100% С8• Thu 21 May 10:15:32•••EDQHomeDMsActivityFilesLater•••More+→Describe what you are looking forJiminny ...ThreadsC9 Unreadsà platform-inner-teamGalya Dimitrova, Nikolay Yankov, and youa ThreadsHuddles• Drafts & sentDirectories01External connections* Starred& jiminny-x-integrati...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity_lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# supportLukas Kovalik 31 minutes agoдобро утро, тази страница не трябва ли да япоказваме само ако няма reportsCleanShot 2026-05-21 at [EMAIL] your questions into ongoing insightsGalya Dimitrova 23 minutes agoДа, би трябвалоLukas Kovalik 22 minutes agoче сега ми го показва и ако имам reportLukas Kovalik 21 minutes agoна neptuneNewNikolay Yankov 6 minutes agoвсичко е наредReply...Also send to à platform-inner-team+AaYou're up to date...
|
NULL
|
4556108544665021624
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpscreelDOCKER0 SlackFileEditViewGoHistoryWindowHelpscreelDOCKER0 81DEV (-zsh)₴2APP (-zsh)whisper_init_state:whisper_init_state:kv padsize=2.36 MBcompute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)=whisper_init_state:compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocatingwhisper_backend_init_gpu: device 0: Metal (type:1)whisper_backend_init_gpu: found GPU device 0: Metal (type: 1, cnt: 0)whisper_backend_init_gpu: using Metal backendggml_metal_init: allocatingggml_metal_init: found device: Apple M1ggml_metal_init: picking default device: Apple M1ggml_metal_init:use fusion= trueggml_metal_init:use concurrency= trueggml_metal_init: use graph optimizetruewhisper_backend_init: using BLAS backendwhisper_init_state: kv self size3.15 MBwhisper_init_state: kv cross size =9.44 MBwhisper_init_state: kv padsize =2.36 MBwhisper_init_state: compute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)=whisper_init_state: compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocatingwhisper_backend_init_gpu:device 0: Metal (type: 1)whisper_backend_init_gpu: found GPUdevice 0: Metal (type: 1, cnt: 0)whisper_backend_init_gpu: using Metal backendggml_metal_init: allocatingggml_metal_init: found device: Apple M1ggml_metal_init: picking default device: Apple M1ggml_metal_init:use fusion= trueggml_metal_init:use concurrencytrueggml_metal_init:use graph optimizetruewhisper_backend_init: using BLAS backendwhisper_init_state: kv self size3.15 MBwhisper_init_state: kv cross size =9.44 MBwhisper_init_state: kv padsize=2.36 MBwhisper_init_state: compute buffer (conv)whisper_init_state: compute buffer (encode) =whisper_init_state: compute buffer (cross)whisper_init_state: compute buffer (decode) =14.17 MB65.96 MB8.50 MB96.83 MBggml_metal_free: deallocating2026-05-21T10:13:41.913005ZINFO screenpipe_audio::audio_manager::manager: reconciliatior2026-05-21710:15:15.5768762INFO screenpipe_engine::event_driven_capture: content dedup:j Support Daily - in 4h 45 m100% С8• Thu 21 May 10:15:32•••EDQHomeDMsActivityFilesLater•••More+→Describe what you are looking forJiminny ...ThreadsC9 Unreadsà platform-inner-teamGalya Dimitrova, Nikolay Yankov, and youa ThreadsHuddles• Drafts & sentDirectories01External connections* Starred& jiminny-x-integrati...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# curiosity_lab# engineering# general# happy_birthday# jiminny-bg# platform-tickets# product_launches# random# releases# sofia-office# supportLukas Kovalik 31 minutes agoдобро утро, тази страница не трябва ли да япоказваме само ако няма reportsCleanShot 2026-05-21 at [EMAIL] your questions into ongoing insightsGalya Dimitrova 23 minutes agoДа, би трябвалоLukas Kovalik 22 minutes agoче сега ми го показва и ако имам reportLukas Kovalik 21 minutes agoна neptuneNewNikolay Yankov 6 minutes agoвсичко е наредReply...Also send to à platform-inner-team+AaYou're up to date...
|
65515
|
NULL
|
NULL
|
NULL
|
|
65528
|
2311
|
1
|
2026-05-21T07:48:32.282959+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349712282_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
slackFV [EMAIL]=.phounit.result.cacheE .prettierig slackFV [EMAIL]=.phounit.result.cacheE .prettierignore= windsurtrulesphpde neloer.onophpide heloer_models.ohophp artisanM'CLAUDE.mdcomooser.isoncomooser.lockdependency-checker.json8 dev ison= ids tytl=infection.ison.distMLINSTAlI.mdMLINTERNAI WERHOOK SFTUP mdEjiminny_storageMiliconsos mdllM Makefile1рackace-lock. sonE phpstan.neon.dist= phostan-baseline.neon<> phpunit.xmlTe raw sal query.saM+ README.mdsô sonar-proiect propertiesE test.ov‹> Untitled Diaaram.xmliusvetur.confio.isM+ WEBHOOK FILTERING_IMPLEMENTATION.mo>1h External Librariesv =° Scratches and Consolesv batabase Consolesv AEU# console (EUlI DEAL RISKS (SU1A DITEUlA EU (EU]v Ajiminny@localhostA console (jiminny@localhost]# Di [liminny@localhostlA HS local [iiminny@localhostlA SF ['iiminny@localhost]& zoho dev [iiminny@localhost]V & PRODA console [PROD]& console 1 [PROD1LDIIPRODCActivityController.ong© ConvertLeadActivities.php= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console (PROD] X# console [euyLeadkepository.ong© PurgeLookupCache.php© SyncPlanhat.phpo synerorlannatpnpA console [STAGING]AcuivityLeadConverted.php© UserAutomatedreporscontroller.phpx© PlaybackController.php)Inteqrationapp/service.onoC) LeadConverted.ono570571C) CreateCommentedEvent.php(C) CreateSmsSentEvent.phpC) PlanhatActivityListener.php© AskAnythingPromptService.php572(C) AutomatedReportsRepositorv.php© AutomatedReportsCommand.phpphp api_v2.phpC) RequestGenerateReportJob.php573574© AutomatedReportResult.php(C) AutomatedReport.oho575use Jiminny \Services \Kiosk \AutomatedReports \ReportSortDirection;A1422 ^ v 576use Jiminny \Services\PlanhatService;use ectumtnace hutp kequestuse Inrowable,class UserAutomatedReportsController extends Controllenpublic const int RESULTS PER_ PAGE = 25:public const string SORT COLUMN = 'sort column':public const strina SORT DIRECTION = "sort direction':public function constructprivate readonly AutomatedReportsRepository $automatedReportsRepository.private readonly AutomatedRenortsService SautomatedRenortsServicelorivate readoni.v AniResponseService samiResponseServi.ce.orivate readoni.v Resnonse Sresnonse.private readonly PlanhatService $planhatService1...poST lanilv1lautomated-renorts/interest 1usadepublic function trackInterest(Request $request): JsonResponse(...}* Othnows ApplicationException58(09 >GET /api/v1/automated-reportspublic function list(Request Srequest): JsonResponse(...}125 0)DELETE/api/vl/automated-reports/kuuidpublic function delete(Request Srequest. string Suuid): JsonResponsef...} -609610--611612613616Tx: AutovSo jiminny vCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE'' END) AS USEr_i041 A1 A40 V65 ^sa.*t.owner_id FROM social_accounts saNoun usens u on u.ide sa.sociablle 1diJOIN 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-7345381b6044|"pat", "podcast"SELECT * FROM automated report results WHERE uuid to bin( '822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMWHERE id = 1919:select * fromautomated report results WHERE report id = 54:select * from opportunities where id = 7594349:SELECT * FROM teams WHERE name LIKE '%Lesg': # 711. 692. [EMAIL] * from playbooks where team_id = 711; # event 226147SELEd * FROM Dlavbook catedonies WHERE Dlavbook 1d = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event':SELECT * SROM eom fields WHEREI1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;SELSCTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '• END) AS user_id,v.emailsa.*,t.owner id FROM social accounts saJOIN users u on v.id = sa.sociable idJOIN teams t 1..n<->1: on t.id = u.team idWHERE U.team_id = 711 and sa.provider = 'salesforce";SELECT * FROM crm profiles cp JOIN users u 1..n<->1: on u.id = co.user id WHERE u.team_id = 711:select * from leadsselect * from calendarsSELECTS 0 hllsupoont Dally • In 4h 40m100% 52• Inu z1 May 10.10.34AskJiminnyReportActivityServiceTestvCascadePlaybook Categorie+0 ..connected. How isi. Can we vali eoneactivity tyee nith orit cabest aeeiewmane o, W dati anwsyggesinsingrow hohow to distinguishThoughts68 Fast Context How are playbook_categories populated and synced from crm_fields / crm_field_values... in 4.14s >Explored 4 files and searched 5 queriesRead ImportActivitvTypes.php #L95-104Searched olavbook cateaories in databaselmiarations (4) >egories|playbook_categories.*create in database/migrations (0)searched playoook_categor in database/migrations (<)Searched activity type in database/migrations (4)SurtingAsk anvthina (*4L)@ Codo Caudo Onue 17 ModiumNN Windeurf Toame 504.60...
|
NULL
|
4726481696127267389
|
NULL
|
click
|
ocr
|
NULL
|
slackFV [EMAIL]=.phounit.result.cacheE .prettierig slackFV [EMAIL]=.phounit.result.cacheE .prettierignore= windsurtrulesphpde neloer.onophpide heloer_models.ohophp artisanM'CLAUDE.mdcomooser.isoncomooser.lockdependency-checker.json8 dev ison= ids tytl=infection.ison.distMLINSTAlI.mdMLINTERNAI WERHOOK SFTUP mdEjiminny_storageMiliconsos mdllM Makefile1рackace-lock. sonE phpstan.neon.dist= phostan-baseline.neon<> phpunit.xmlTe raw sal query.saM+ README.mdsô sonar-proiect propertiesE test.ov‹> Untitled Diaaram.xmliusvetur.confio.isM+ WEBHOOK FILTERING_IMPLEMENTATION.mo>1h External Librariesv =° Scratches and Consolesv batabase Consolesv AEU# console (EUlI DEAL RISKS (SU1A DITEUlA EU (EU]v Ajiminny@localhostA console (jiminny@localhost]# Di [liminny@localhostlA HS local [iiminny@localhostlA SF ['iiminny@localhost]& zoho dev [iiminny@localhost]V & PRODA console [PROD]& console 1 [PROD1LDIIPRODCActivityController.ong© ConvertLeadActivities.php= laravel.log4 SF jiminny@localhost]4 HS_local [jiminny@localhost]& console (PROD] X# console [euyLeadkepository.ong© PurgeLookupCache.php© SyncPlanhat.phpo synerorlannatpnpA console [STAGING]AcuivityLeadConverted.php© UserAutomatedreporscontroller.phpx© PlaybackController.php)Inteqrationapp/service.onoC) LeadConverted.ono570571C) CreateCommentedEvent.php(C) CreateSmsSentEvent.phpC) PlanhatActivityListener.php© AskAnythingPromptService.php572(C) AutomatedReportsRepositorv.php© AutomatedReportsCommand.phpphp api_v2.phpC) RequestGenerateReportJob.php573574© AutomatedReportResult.php(C) AutomatedReport.oho575use Jiminny \Services \Kiosk \AutomatedReports \ReportSortDirection;A1422 ^ v 576use Jiminny \Services\PlanhatService;use ectumtnace hutp kequestuse Inrowable,class UserAutomatedReportsController extends Controllenpublic const int RESULTS PER_ PAGE = 25:public const string SORT COLUMN = 'sort column':public const strina SORT DIRECTION = "sort direction':public function constructprivate readonly AutomatedReportsRepository $automatedReportsRepository.private readonly AutomatedRenortsService SautomatedRenortsServicelorivate readoni.v AniResponseService samiResponseServi.ce.orivate readoni.v Resnonse Sresnonse.private readonly PlanhatService $planhatService1...poST lanilv1lautomated-renorts/interest 1usadepublic function trackInterest(Request $request): JsonResponse(...}* Othnows ApplicationException58(09 >GET /api/v1/automated-reportspublic function list(Request Srequest): JsonResponse(...}125 0)DELETE/api/vl/automated-reports/kuuidpublic function delete(Request Srequest. string Suuid): JsonResponsef...} -609610--611612613616Tx: AutovSo jiminny vCONCAT(u.id, CASE WHEN U.id = t.owner_id THEN' (owner)' ELSE'' END) AS USEr_i041 A1 A40 V65 ^sa.*t.owner_id FROM social_accounts saNoun usens u on u.ide sa.sociablle 1diJOIN 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-7345381b6044|"pat", "podcast"SELECT * FROM automated report results WHERE uuid to bin( '822fa41b-afd3-43a9-a248-86b0e36f3131') = uuid;select * from automated_report_results order by id desc;SELECT * FROMWHERE id = 1919:select * fromautomated report results WHERE report id = 54:select * from opportunities where id = 7594349:SELECT * FROM teams WHERE name LIKE '%Lesg': # 711. 692. [EMAIL] * from playbooks where team_id = 711; # event 226147SELEd * FROM Dlavbook catedonies WHERE Dlavbook 1d = 55151SELECT * FROM crm_fields WHERE crm_configuration_id = 692 and object_type = 'event':SELECT * SROM eom fields WHEREI1d = 2261471SELECT * FROM crm_field_values WHERE crm_field_id = 226147;SELECT * FROM crm_configurations WHERE id = 692;SELSCTCONCAT(u.id, CASE WHEN u.id = t.owner_id THEN ' (owner)' ELSE '• END) AS user_id,v.emailsa.*,t.owner id FROM social accounts saJOIN users u on v.id = sa.sociable idJOIN teams t 1..n<->1: on t.id = u.team idWHERE U.team_id = 711 and sa.provider = 'salesforce";SELECT * FROM crm profiles cp JOIN users u 1..n<->1: on u.id = co.user id WHERE u.team_id = 711:select * from leadsselect * from calendarsSELECTS 0 hllsupoont Dally • In 4h 40m100% 52• Inu z1 May 10.10.34AskJiminnyReportActivityServiceTestvCascadePlaybook Categorie+0 ..connected. How isi. Can we vali eoneactivity tyee nith orit cabest aeeiewmane o, W dati anwsyggesinsingrow hohow to distinguishThoughts68 Fast Context How are playbook_categories populated and synced from crm_fields / crm_field_values... in 4.14s >Explored 4 files and searched 5 queriesRead ImportActivitvTypes.php #L95-104Searched olavbook cateaories in databaselmiarations (4) >egories|playbook_categories.*create in database/migrations (0)searched playoook_categor in database/migrations (<)Searched activity type in database/migrations (4)SurtingAsk anvthina (*4L)@ Codo Caudo Onue 17 ModiumNN Windeurf Toame 504.60...
|
65514
|
NULL
|
NULL
|
NULL
|
|
65529
|
2310
|
2
|
2026-05-21T07:49:03.363650+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349743363_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65530
|
2311
|
2
|
2026-05-21T07:49:04.048242+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349744048_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
65531
|
2310
|
3
|
2026-05-21T07:49:33.684989+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349773684_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65529
|
NULL
|
NULL
|
NULL
|
|
65532
|
2311
|
3
|
2026-05-21T07:49:34.429722+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349774429_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65530
|
NULL
|
NULL
|
NULL
|
|
65533
|
2310
|
4
|
2026-05-21T07:50:04.048639+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349804048_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65529
|
NULL
|
NULL
|
NULL
|
|
65534
|
2311
|
4
|
2026-05-21T07:50:04.854754+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349804854_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65530
|
NULL
|
NULL
|
NULL
|
|
65535
|
2310
|
5
|
2026-05-21T07:50:34.376508+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349834376_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65529
|
NULL
|
NULL
|
NULL
|
|
65536
|
2311
|
5
|
2026-05-21T07:50:35.284631+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349835284_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65530
|
NULL
|
NULL
|
NULL
|
|
65537
|
2310
|
6
|
2026-05-21T07:51:04.690928+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349864690_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65529
|
NULL
|
NULL
|
NULL
|
|
65538
|
2311
|
6
|
2026-05-21T07:51:05.654301+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349865654_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65530
|
NULL
|
NULL
|
NULL
|
|
65539
|
2310
|
7
|
2026-05-21T07:51:35.019566+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349895019_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65529
|
NULL
|
NULL
|
NULL
|
|
65540
|
2311
|
7
|
2026-05-21T07:51:36.020770+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349896020_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65530
|
NULL
|
NULL
|
NULL
|
|
65541
|
2310
|
8
|
2026-05-21T07:52:05.334456+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349925334_m1.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65529
|
NULL
|
NULL
|
NULL
|
|
65542
|
2311
|
8
|
2026-05-21T07:52:06.394502+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-21/1779 /Users/lukas/.screenpipe/data/data/2026-05-21/1779349926394_m2.jpg...
|
PhpStorm
|
faVsco.js – SyncCrmFieldsTrait.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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
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/>74 incoming commits<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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\n };\n }\n}","depth":4,"bounds":{"left":0.15990691,"top":0.007980846,"width":0.2662899,"height":0.98324025},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\IntegrationApp\\ServiceTraits;\n\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Repositories\\Crm\\FieldRepository;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\Config\\IntegrationAppProviders;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmField;\nuse Jiminny\\Services\\Crm\\IntegrationApp\\DTO\\CrmFieldDefinitionsManager;\nuse RuntimeException;\n\ntrait SyncCrmFieldsTrait\n{\n public function syncFields(): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {\n $fieldManager = new CrmFieldDefinitionsManager();\n\n foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));\n $this->syncObjectTypeFields($fieldType, $crmFields);\n }\n }\n\n return;\n }\n\n foreach ($this->getFieldTypes() as $object) {\n $this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));\n $fields = $this->client->getObjectFields($object);\n foreach ($fields as $fieldDto) {\n $field = $this->importCrmField($fieldDto);\n if ($field->isPicklist()) {\n $this->importPicklistValues($field);\n }\n }\n }\n }\n\n private function syncObjectTypeFields(string $fieldType, array $crmFields): void\n {\n foreach ($crmFields as $crmField) {\n $this->syncCrmField($fieldType, $crmField);\n }\n }\n\n private function syncCrmField(string $fieldType, array $crmField): void\n {\n $fieldData = [\n 'crm_provider_id' => $crmField['crm_id'],\n 'object_type' => $fieldType,\n 'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),\n 'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),\n 'type' => $crmField['type'],\n 'is_selectable' => $crmField['is_selectable'] ?? true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n $field = $fieldRepository->upsertField($this->config, $fieldData);\n\n $this->logger->info('[integration-app] Synced field', [\n 'field' => $field->getLabel(),\n 'team_id' => $this->team?->getId(),\n ]);\n\n $field->refresh();\n if ($field->getType() === Field::TYPE_PICKLIST) {\n $this->importPicklistValues($field);\n }\n }\n\n private function importCrmField(CrmField $crmField): Field\n {\n $fieldData = [\n 'crm_provider_id' => $crmField->getExternalId(),\n 'object_type' => $crmField->getObjectType(),\n 'label' => $crmField->getLabel()\n ? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)\n : '',\n 'description' => $crmField->getDescription()\n ? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)\n : null,\n 'type' => $crmField->getType(),\n 'is_selectable' => true,\n ];\n\n /** @var FieldRepository $fieldRepository */\n $fieldRepository = app(FieldRepository::class);\n\n return $fieldRepository->upsertField($this->config, $fieldData);\n }\n\n /**\n * Works together with SyncTeamMetadata command if new field is added and needs to be synced.\n */\n public function syncField(Field $field): void\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldId = $field->getCrmProviderId();\n $fieldType = $field->getObjectType();\n\n $fieldManager = new CrmFieldDefinitionsManager();\n $fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);\n\n $this->syncCrmField($fieldType, $fieldDefinition);\n } else {\n $fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());\n $this->importCrmField($fieldDto);\n }\n }\n\n public function importPicklistValues(Field $field): array\n {\n if (! $this->editionConfig->supportsCrmFieldsSync()) {\n $fieldManager = new CrmFieldDefinitionsManager();\n $picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());\n } else {\n if (! $field->isPicklist()) {\n return [];\n }\n\n $picklistValues = $this->client->getPicklistValues(\n $field->getObjectType(),\n $field->getCrmProviderId(),\n );\n }\n\n if (empty($picklistValues)) {\n return [];\n }\n\n return $this->importOptions($field, $picklistValues);\n }\n\n private function importOptions(Field $field, array $options): array\n {\n $fieldValues = [];\n $sequence = 0;\n $fieldRepo = app(FieldRepository::class);\n\n foreach ($options as $option) {\n $value = [\n 'value' => $option,\n 'label' => $option,\n 'sequence' => $sequence++,\n ];\n\n $fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);\n }\n\n return $fieldValues;\n }\n\n protected function getFieldTypes(): array\n {\n return match ($this->config->getEdition()) {\n IntegrationAppProviders::MS_DYNAMICS => [\n Field::OBJECT_OPPORTUNITY,\n Field::OBJECT_LEAD,\n Field::OBJECT_CONTACT,\n Field::OBJECT_ACCOUNT,\n Field::OBJECT_TASK,\n ],\n default => throw new RuntimeException('Unsupported CRM provider')\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":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\n }\n}","depth":4,"bounds":{"left":0.44547874,"top":0.09736632,"width":0.29022607,"height":0.8818835},"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Helpers;\n\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Crm\\Field;\nuse Jiminny\\Models\\Crm\\FieldValue;\nuse Jiminny\\Models\\Feature\\FeatureEnum;\nuse Jiminny\\Models\\Playbook;\nuse Jiminny\\Models\\PlaybookCategory;\n\n/**\n * A collection of DB-related internal functionalities or all CRMs.\n */\nclass CrmHelperRepository\n{\n public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory\n {\n /** @var ?PlaybookCategory */\n return $playbook->categories()\n ->where('name', trim($categoryName))\n ->orderBy('id', 'desc')\n ->first();\n }\n\n public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue\n {\n /** @var ?FieldValue */\n return $activityField->values()\n ->where('label', $categoryName)\n ->first();\n }\n\n public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool\n {\n return $activityField->values()\n ->where('label', $categoryName)\n ->exists();\n }\n\n public function hasFeature(Activity $activity, FeatureEnum $featureName): bool\n {\n return $activity->getUser()\n ->getTeam()\n ->hasFeature($featureName);\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}]...
|
1405148025673151884
|
-8132123592518247404
|
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
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\IntegrationApp\ServiceTraits;
use Jiminny\Models\Crm\Field;
use Jiminny\Repositories\Crm\FieldRepository;
use Jiminny\Services\Crm\IntegrationApp\Config\IntegrationAppProviders;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmField;
use Jiminny\Services\Crm\IntegrationApp\DTO\CrmFieldDefinitionsManager;
use RuntimeException;
trait SyncCrmFieldsTrait
{
public function syncFields(): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
if ($this->config->getEdition() === IntegrationAppProviders::ZOHO_CRM) {
$fieldManager = new CrmFieldDefinitionsManager();
foreach ($fieldManager->getFieldDefinitions() as $fieldType => $crmFields) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $fieldType));
$this->syncObjectTypeFields($fieldType, $crmFields);
}
}
return;
}
foreach ($this->getFieldTypes() as $object) {
$this->logger->info(sprintf('[integration-app] Syncing %s fields.', $object));
$fields = $this->client->getObjectFields($object);
foreach ($fields as $fieldDto) {
$field = $this->importCrmField($fieldDto);
if ($field->isPicklist()) {
$this->importPicklistValues($field);
}
}
}
}
private function syncObjectTypeFields(string $fieldType, array $crmFields): void
{
foreach ($crmFields as $crmField) {
$this->syncCrmField($fieldType, $crmField);
}
}
private function syncCrmField(string $fieldType, array $crmField): void
{
$fieldData = [
'crm_provider_id' => $crmField['crm_id'],
'object_type' => $fieldType,
'label' => mb_strimwidth($crmField['label'], 0, Field::LABEL_MAX_LENGTH),
'description' => mb_strimwidth($crmField['description'], 0, Field::DESCRIPTION_MAX_LENGTH),
'type' => $crmField['type'],
'is_selectable' => $crmField['is_selectable'] ?? true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
$field = $fieldRepository->upsertField($this->config, $fieldData);
$this->logger->info('[integration-app] Synced field', [
'field' => $field->getLabel(),
'team_id' => $this->team?->getId(),
]);
$field->refresh();
if ($field->getType() === Field::TYPE_PICKLIST) {
$this->importPicklistValues($field);
}
}
private function importCrmField(CrmField $crmField): Field
{
$fieldData = [
'crm_provider_id' => $crmField->getExternalId(),
'object_type' => $crmField->getObjectType(),
'label' => $crmField->getLabel()
? mb_strimwidth($crmField->getLabel(), 0, Field::LABEL_MAX_LENGTH)
: '',
'description' => $crmField->getDescription()
? mb_strimwidth($crmField->getDescription(), 0, Field::DESCRIPTION_MAX_LENGTH)
: null,
'type' => $crmField->getType(),
'is_selectable' => true,
];
/** @var FieldRepository $fieldRepository */
$fieldRepository = app(FieldRepository::class);
return $fieldRepository->upsertField($this->config, $fieldData);
}
/**
* Works together with SyncTeamMetadata command if new field is added and needs to be synced.
*/
public function syncField(Field $field): void
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldId = $field->getCrmProviderId();
$fieldType = $field->getObjectType();
$fieldManager = new CrmFieldDefinitionsManager();
$fieldDefinition = $fieldManager->findByCrmId($fieldId, $fieldType);
$this->syncCrmField($fieldType, $fieldDefinition);
} else {
$fieldDto = $this->client->getField($field->getObjectType(), $field->getCrmProviderId());
$this->importCrmField($fieldDto);
}
}
public function importPicklistValues(Field $field): array
{
if (! $this->editionConfig->supportsCrmFieldsSync()) {
$fieldManager = new CrmFieldDefinitionsManager();
$picklistValues = $fieldManager->getPicklistValues($field->getCrmProviderId());
} else {
if (! $field->isPicklist()) {
return [];
}
$picklistValues = $this->client->getPicklistValues(
$field->getObjectType(),
$field->getCrmProviderId(),
);
}
if (empty($picklistValues)) {
return [];
}
return $this->importOptions($field, $picklistValues);
}
private function importOptions(Field $field, array $options): array
{
$fieldValues = [];
$sequence = 0;
$fieldRepo = app(FieldRepository::class);
foreach ($options as $option) {
$value = [
'value' => $option,
'label' => $option,
'sequence' => $sequence++,
];
$fieldValues[] = $fieldRepo->updateOrCreateFieldValue($field, $value);
}
return $fieldValues;
}
protected function getFieldTypes(): array
{
return match ($this->config->getEdition()) {
IntegrationAppProviders::MS_DYNAMICS => [
Field::OBJECT_OPPORTUNITY,
Field::OBJECT_LEAD,
Field::OBJECT_CONTACT,
Field::OBJECT_ACCOUNT,
Field::OBJECT_TASK,
],
default => throw new RuntimeException('Unsupported CRM provider')
};
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Helpers;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Field;
use Jiminny\Models\Crm\FieldValue;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Playbook;
use Jiminny\Models\PlaybookCategory;
/**
* A collection of DB-related internal functionalities or all CRMs.
*/
class CrmHelperRepository
{
public function getPlaybookCategory(Playbook $playbook, string $categoryName): ?PlaybookCategory
{
/** @var ?PlaybookCategory */
return $playbook->categories()
->where('name', trim($categoryName))
->orderBy('id', 'desc')
->first();
}
public function getActivityFieldValueByName(Field $activityField, string $categoryName): ?FieldValue
{
/** @var ?FieldValue */
return $activityField->values()
->where('label', $categoryName)
->first();
}
public function existsActivityFieldValueByName(Field $activityField, string $categoryName): bool
{
return $activityField->values()
->where('label', $categoryName)
->exists();
}
public function hasFeature(Activity $activity, FeatureEnum $featureName): bool
{
return $activity->getUser()
->getTeam()
->hasFeature($featureName);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
65530
|
NULL
|
NULL
|
NULL
|