|
54908
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
class AskJiminnyReportsController extends Controller
{
public function __construct(
private readonly AutomatedReportsService $automatedReportsService,
private readonly LoggerInterface $logger,
) {
}
private function isNotOwnedByUser(AutomatedReport $report, User $user): bool
{
return $report->getTeamId() !== $user->getTeamId()
|| $report->getAttribute('created_by') !== $user->getId();
}
public function getFormData(Request $request, ?string $uuid = null): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $uuid ? $this->automatedReportsService->getReport($uuid) : null;
return new JsonResponse(
$this->automatedReportsService->getAskJiminnyReportFormData($user, $report)
);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report form data', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch form data'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Create a new Ask Jiminny report.
*/
public function create(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$data = $this->automatedReportsService->createAskJiminnyReport($request->all(), $user);
return new JsonResponse($data);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to create Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to create report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Update an existing Ask Jiminny report.
*/
public function update(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReport($report, $request->all(), $user);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to update Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to update report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Toggle Ask Jiminny report status (enable/disable).
*/
public function toggleStatus(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReportStatus(
$report,
(bool) $request->input('enabled'),
);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to toggle Ask Jiminny report status', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to toggle report status'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* List all Ask Jiminny reports.
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$sortColumn = $request->input('sort_column', 'created_at');
$sortDirection = $request->input('sort_direction', 'desc');
$data = $this->automatedReportsService->listAskJiminnyReports($user, $sortColumn, $sortDirection);
return new JsonResponse($data);
} catch (Throwable $e) {
$this->logger->error('Failed to list Ask Jiminny reports', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch reports'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Get a single Ask Jiminny report.
*/
public function get(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
return new JsonResponse($this->automatedReportsService->get($uuid));
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to get Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getReportsCount(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$resultsCount = $this->automatedReportsService->getReportResults($report)->count();
return new JsonResponse(['count' => $resultsCount]);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to count report results', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'report_uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to count report results'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Delete an Ask Jiminny report.
*/
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
if ($request->boolean('delete_generated_reports')) {
$this->automatedReportsService->deleteReportResults($uuid);
}
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$this->automatedReportsService->delete($uuid);
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to delete Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to delete report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getFilters(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$filters = $this->automatedReportsService->getAskJiminnyReportFilters($user);
return new JsonResponse(['filters' => $filters]);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report filters', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch filters'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
54908
|
|
54909
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
class AskJiminnyReportsController extends Controller
{
public function __construct(
private readonly AutomatedReportsService $automatedReportsService,
private readonly LoggerInterface $logger,
) {
}
private function isNotOwnedByUser(AutomatedReport $report, User $user): bool
{
return $report->getTeamId() !== $user->getTeamId()
|| $report->getAttribute('created_by') !== $user->getId();
}
public function getFormData(Request $request, ?string $uuid = null): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $uuid ? $this->automatedReportsService->getReport($uuid) : null;
return new JsonResponse(
$this->automatedReportsService->getAskJiminnyReportFormData($user, $report)
);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report form data', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch form data'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Create a new Ask Jiminny report.
*/
public function create(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$data = $this->automatedReportsService->createAskJiminnyReport($request->all(), $user);
return new JsonResponse($data);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to create Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to create report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Update an existing Ask Jiminny report.
*/
public function update(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReport($report, $request->all(), $user);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to update Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to update report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Toggle Ask Jiminny report status (enable/disable).
*/
public function toggleStatus(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReportStatus(
$report,
(bool) $request->input('enabled'),
);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to toggle Ask Jiminny report status', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to toggle report status'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* List all Ask Jiminny reports.
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$sortColumn = $request->input('sort_column', 'created_at');
$sortDirection = $request->input('sort_direction', 'desc');
$data = $this->automatedReportsService->listAskJiminnyReports($user, $sortColumn, $sortDirection);
return new JsonResponse($data);
} catch (Throwable $e) {
$this->logger->error('Failed to list Ask Jiminny reports', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch reports'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Get a single Ask Jiminny report.
*/
public function get(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
return new JsonResponse($this->automatedReportsService->get($uuid));
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to get Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getReportsCount(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$resultsCount = $this->automatedReportsService->getReportResults($report)->count();
return new JsonResponse(['count' => $resultsCount]);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to count report results', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'report_uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to count report results'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Delete an Ask Jiminny report.
*/
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
if ($request->boolean('delete_generated_reports')) {
$this->automatedReportsService->deleteReportResults($uuid);
}
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$this->automatedReportsService->delete($uuid);
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to delete Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to delete report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getFilters(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$filters = $this->automatedReportsService->getAskJiminnyReportFilters($user);
return new JsonResponse(['filters' => $filters]);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report filters', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch filters'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
54909
|
|
54928
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
class AskJiminnyReportsController extends Controller
{
public function __construct(
private readonly AutomatedReportsService $automatedReportsService,
private readonly LoggerInterface $logger,
) {
}
private function isNotOwnedByUser(AutomatedReport $report, User $user): bool
{
return $report->getTeamId() !== $user->getTeamId()
|| $report->getAttribute('created_by') !== $user->getId();
}
public function getFormData(Request $request, ?string $uuid = null): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $uuid ? $this->automatedReportsService->getReport($uuid) : null;
return new JsonResponse(
$this->automatedReportsService->getAskJiminnyReportFormData($user, $report)
);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report form data', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch form data'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Create a new Ask Jiminny report.
*/
public function create(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$data = $this->automatedReportsService->createAskJiminnyReport($request->all(), $user);
return new JsonResponse($data);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to create Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to create report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Update an existing Ask Jiminny report.
*/
public function update(Request $request, string $uuid): JsonResponse
{
\Illuminate\Support\Facades\Log::channel('custom_channel')->info("UPDATE");
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReport($report, $request->all(), $user);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to update Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to update report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Toggle Ask Jiminny report status (enable/disable).
*/
public function toggleStatus(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReportStatus(
$report,
(bool) $request->input('enabled'),
);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to toggle Ask Jiminny report status', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to toggle report status'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* List all Ask Jiminny reports.
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$sortColumn = $request->input('sort_column', 'created_at');
$sortDirection = $request->input('sort_direction', 'desc');
$data = $this->automatedReportsService->listAskJiminnyReports($user, $sortColumn, $sortDirection);
return new JsonResponse($data);
} catch (Throwable $e) {
$this->logger->error('Failed to list Ask Jiminny reports', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch reports'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Get a single Ask Jiminny report.
*/
public function get(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
return new JsonResponse($this->automatedReportsService->get($uuid));
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to get Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getReportsCount(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$resultsCount = $this->automatedReportsService->getReportResults($report)->count();
return new JsonResponse(['count' => $resultsCount]);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to count report results', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'report_uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to count report results'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Delete an Ask Jiminny report.
*/
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
if ($request->boolean('delete_generated_reports')) {
$this->automatedReportsService->deleteReportResults($uuid);
}
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$this->automatedReportsService->delete($uuid);
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to delete Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to delete report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getFilters(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$filters = $this->automatedReportsService->getAskJiminnyReportFilters($user);
return new JsonResponse(['filters' => $filters]);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report filters', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch filters'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
60
Previous Highlighted Error
Next Highlighted Error
[2026-04-20 09:23:02] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bd919c4f-72bd-41ef-a33f-5e863a4150c6","trace_id":"51d1cd7c-9cf3-4132-9ec0-2fb7d43f5beb"}
[2026-04-20 09:23:02] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"bd919c4f-72bd-41ef-a33f-5e863a4150c6","trace_id":"51d1cd7c-9cf3-4132-9ec0-2fb7d43f5beb"}
[2026-04-20 09:23:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bd919c4f-72bd-41ef-a33f-5e863a4150c6","trace_id":"51d1cd7c-9cf3-4132-9ec0-2fb7d43f5beb"}
[2026-04-20 09:23:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"711bcf80-8f66-48c9-b0d2-f5f5bee339a8","trace_id":"ab11a275-1e81-40a6-af55-fce0a815cee0"}
[2026-04-20 09:23:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"711bcf80-8f66-48c9-b0d2-f5f5bee339a8","trace_id":"ab11a275-1e81-40a6-af55-fce0a815cee0"}
[2026-04-20 09:23:07] local.NOTICE: Monitoring start {"correlation_id":"634afa45-c9f6-4cac-a8ec-f9478f336ad1","trace_id":"98db99c7-aaa1-4088-8fed-6d86109538a5"}
[2026-04-20 09:23:07] local.NOTICE: Monitoring end {"correlation_id":"634afa45-c9f6-4cac-a8ec-f9478f336ad1","trace_id":"98db99c7-aaa1-4088-8fed-6d86109538a5"}
[2026-04-20 09:23:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9f7e4df6-a0ba-4cf1-9c8b-be88ed53729b","trace_id":"1e60fe27-016f-4500-986b-8b9cf57f5132"}
[2026-04-20 09:23:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9f7e4df6-a0ba-4cf1-9c8b-be88ed53729b","trace_id":"1e60fe27-016f-4500-986b-8b9cf57f5132"}
[2026-04-20 09:23:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"238faedf-31c7-4d57-91db-03dd17f7736c","trace_id":"a13a2236-0a00-4ede-9f9e-e05005ae45cc"}
[2026-04-20 09:23:15] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"238faedf-31c7-4d57-91db-03dd17f7736c","trace_id":"a13a2236-0a00-4ede-9f9e-e05005ae45cc"}
[2026-04-20 09:23:15] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"238faedf-31c7-4d57-91db-03dd17f7736c","trace_id":"a13a2236-0a00-4ede-9f9e-e05005ae45cc"}
[2026-04-20 09:23:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"238faedf-31c7-4d57-91db-03dd17f7736c","trace_id":"a13a2236-0a00-4ede-9f9e-e05005ae45cc"}
[2026-04-20 09:23:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6803187a-9bf4-4a9d-9f0b-67a53cba6652","trace_id":"[CREDIT_CARD]-81d3-013d8208d378"}
[2026-04-20 09:23:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6803187a-9bf4-4a9d-9f0b-67a53cba6652","trace_id":"[CREDIT_CARD]-81d3-013d8208d378"}
[2026-04-20 09:23:21] local.INFO: [integration-app] Request {"request":"GET connections","full_target":"connections"} {"correlation_id":"21439dca-dcac-4c23-a474-8522b19e4134","trace_id":"e3622673-a493-4bb0-940e-1bd8810bdf87"}
[2026-04-20 09:23:22] local.INFO: [integration-app] Connection state identified {"teamId":3143,"connection_name":"Connection to 66fe6c913202f3a165e3c14d for Dev Zoho CRM client","remote_connection_id":"69e0b983da98fa74f98aebfb","is_disconnected":false,"is_deactivated":false,"is_valid":true} {"correlation_id":"21439dca-dcac-4c23-a474-8522b19e4134","trace_id":"e3622673-a493-4bb0-940e-1bd8810bdf87"}
[2026-04-20 09:23:46] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":37999,"workerId":"","target":"activities"} {"correlation_id":"a34a26a7-bd0d-4aa3-8635-edd5f603a12f","trace_id":"c10663a0-5ccd-4aae-b19a-89a6e3169e0c"}
[2026-04-20 09:24:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6d00ec8f-1670-4a29-9fbb-a42117ac4826","trace_id":"e2f5b0df-0620-48cb-9fc1-b8b9f080113c"}
[2026-04-20 09:24:09] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"6d00ec8f-1670-4a29-9fbb-a42117ac4826","trace_id":"e2f5b0df-0620-48cb-9fc1-b8b9f080113c"}
[2026-04-20 09:24:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6d00ec8f-1670-4a29-9fbb-a42117ac4826","trace_id":"e2f5b0df-0620-48cb-9fc1-b8b9f080113c"}
[2026-04-20 09:24:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"774f6841-2467-45e8-a0bf-640b437c3053","trace_id":"6a8f1155-e2c2-4cad-a3ce-96f27e8a765a"}
[2026-04-20 09:24:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"774f6841-2467-45e8-a0bf-640b437c3053","trace_id":"6a8f1155-e2c2-4cad-a3ce-96f27e8a765a"}
[2026-04-20 09:24:19] local.NOTICE: Monitoring start {"correlation_id":"485b2dd2-2b44-41e4-9a5d-a573afeeb14b","trace_id":"e77d924f-9b86-46cb-aab8-ce6cadbc2f65"}
[2026-04-20 09:24:19] local.NOTICE: Monitoring end {"correlation_id":"485b2dd2-2b44-41e4-9a5d-a573afeeb14b","trace_id":"e77d924f-9b86-46cb-aab8-ce6cadbc2f65"}
[2026-04-20 09:24:24] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"69765ec9-8785-470e-9404-0647c27dd409","trace_id":"09282a86-9a7e-40b3-96f0-ace2253536fc"}
[2026-04-20 09:24:24] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"69765ec9-8785-470e-9404-0647c27dd409","trace_id":"09282a86-9a7e-40b3-96f0-ace2253536fc"}
[2026-04-20 09:24:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"452d8650-e522-461e-bdee-67bb21b501f7","trace_id":"a2776725-cf4f-404c-8765-a661c330ee19"}
[2026-04-20 09:24:28] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"452d8650-e522-461e-bdee-67bb21b501f7","trace_id":"a2776725-cf4f-404c-8765-a661c330ee19"}
[2026-04-20 09:24:28] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"452d8650-e522-461e-bdee-67bb21b501f7","trace_id":"a2776725-cf4f-404c-8765-a661c330ee19"}
[2026-04-20 09:24:28] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"452d8650-e522-461e-bdee-67bb21b501f7","trace_id":"a2776725-cf4f-404c-8765-a661c330ee19"}
[2026-04-20 09:24:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2be8d23b-fcb1-47f2-a494-e3fdac7bbf9d","trace_id":"03bf6e41-0010-4cdb-aaed-91eda7e9ac05"}
[2026-04-20 09:24:33] local.INFO: Running conference:monitor:count command for activities in (2026-04-20 09:22:00, 2026-04-20 09:24:00] {"correlation_id":"2be8d23b-fcb1-47f2-a494-e3fdac7bbf9d","trace_id":"03bf6e41-0010-4cdb-aaed-91eda7e9ac05"}
[2026-04-20 09:24:33] local.INFO: [conference:monitor:count] No activities found in (2026-04-20 09:22:00, 2026-04-20 09:24:00] {"correlation_id":"2be8d23b-fcb1-47f2-a494-e3fdac7bbf9d","trace_id":"03bf6e41-0010-4cdb-aaed-91eda7e9ac05"}
[2026-04-20 09:24:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2be8d23b-fcb1-47f2-a494-e3fdac7bbf9d","trace_id":"03bf6e41-0010-4cdb-aaed-91eda7e9ac05"}
[2026-04-20 09:24:37] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"657b1638-2b37-4f01-98e2-68a517f992f1","trace_id":"6decb1c6-93a8-4aea-bae1-a5369cf11842"}
[2026-04-20 09:24:37] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"657b1638-2b37-4f01-98e2-68a517f992f1","trace_id":"6decb1c6-93a8-4aea-bae1-a5369cf11842"}
[2026-04-20 09:24:37] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"657b1638-2b37-4f01-98e2-68a517f992f1","trace_id":"6decb1c6-93a8-4aea-bae1-a5369cf11842"}
[2026-04-20 09:24:37] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"657b1638-2b37-4f01-98e2-68a517f992f1","trace_id":"6decb1c6-93a8-4aea-bae1-a5369cf11842"}
[2026-04-20 09:24:38] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"657b1638-2b37-4f01-98e2-68a517f992f1","trace_id":"6decb1c6-93a8-4aea-bae1-a5369cf11842"}
[2026-04-20 09:24:38] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"657b1638-2b37-4f01-98e2-68a517f992f1","trace_id":"6decb1c6-93a8-4aea-bae1-a5369cf11842"}
[2026-04-20 09:24:42] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"9e32ff16-dace-4656-af8d-33272bdd3fe1","trace_id":"10d097f4-2606-4296-a97e-25e45e7e207e"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
54928
|
|
54951
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
class AskJiminnyReportsController extends Controller
{
public function __construct(
private readonly AutomatedReportsService $automatedReportsService,
private readonly LoggerInterface $logger,
) {
}
private function isNotOwnedByUser(AutomatedReport $report, User $user): bool
{
return $report->getTeamId() !== $user->getTeamId()
|| $report->getAttribute('created_by') !== $user->getId();
}
public function getFormData(Request $request, ?string $uuid = null): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $uuid ? $this->automatedReportsService->getReport($uuid) : null;
return new JsonResponse(
$this->automatedReportsService->getAskJiminnyReportFormData($user, $report)
);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report form data', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch form data'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Create a new Ask Jiminny report.
*/
public function create(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$data = $this->automatedReportsService->createAskJiminnyReport($request->all(), $user);
return new JsonResponse($data);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to create Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to create report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Update an existing Ask Jiminny report.
*/
public function update(Request $request, string $uuid): JsonResponse
{
\Illuminate\Support\Facades\Log::channel('custom_channel')->info("UPDATE");
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReport($report, $request->all(), $user);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to update Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to update report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Toggle Ask Jiminny report status (enable/disable).
*/
public function toggleStatus(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReportStatus(
$report,
(bool) $request->input('enabled'),
);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to toggle Ask Jiminny report status', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to toggle report status'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* List all Ask Jiminny reports.
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$sortColumn = $request->input('sort_column', 'created_at');
$sortDirection = $request->input('sort_direction', 'desc');
$data = $this->automatedReportsService->listAskJiminnyReports($user, $sortColumn, $sortDirection);
return new JsonResponse($data);
} catch (Throwable $e) {
$this->logger->error('Failed to list Ask Jiminny reports', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch reports'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Get a single Ask Jiminny report.
*/
public function get(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
return new JsonResponse($this->automatedReportsService->get($uuid));
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to get Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getReportsCount(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$resultsCount = $this->automatedReportsService->getReportResults($report)->count();
return new JsonResponse(['count' => $resultsCount]);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to count report results', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'report_uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to count report results'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Delete an Ask Jiminny report.
*/
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
if ($request->boolean('delete_generated_reports')) {
$this->automatedReportsService->deleteReportResults($uuid);
}
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$this->automatedReportsService->delete($uuid);
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to delete Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to delete report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getFilters(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$filters = $this->automatedReportsService->getAskJiminnyReportFilters($user);
return new JsonResponse(['filters' => $filters]);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report filters', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch filters'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
[2026-04-20 09:25:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:09] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6a7007ae-7e30-40d3-a9c9-a2ba6ebb4654","trace_id":"df1421c1-9742-4cab-9136-3b91fef3a225"}
[2026-04-20 09:25:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6a7007ae-7e30-40d3-a9c9-a2ba6ebb4654","trace_id":"df1421c1-9742-4cab-9136-3b91fef3a225"}
[2026-04-20 09:25:21] local.NOTICE: Monitoring start {"correlation_id":"b706ba8d-055b-4160-80ad-68f7db04afcb","trace_id":"0b7a1130-21d3-4aba-b7ec-c65d2c0f65b6"}
[2026-04-20 09:25:21] local.NOTICE: Monitoring end {"correlation_id":"b706ba8d-055b-4160-80ad-68f7db04afcb","trace_id":"0b7a1130-21d3-4aba-b7ec-c65d2c0f65b6"}
[2026-04-20 09:25:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"56e73e32-72f0-4918-8a84-53682b5b7df4","trace_id":"842ef1af-6992-4c51-9eca-402784f14ebf"}
[2026-04-20 09:25:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"56e73e32-72f0-4918-8a84-53682b5b7df4","trace_id":"842ef1af-6992-4c51-9eca-402784f14ebf"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
54951
|
|
54952
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
9
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Http\Controllers\API\V2;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
class AskJiminnyReportsController extends Controller
{
public function __construct(
private readonly AutomatedReportsService $automatedReportsService,
private readonly LoggerInterface $logger,
) {
}
private function isNotOwnedByUser(AutomatedReport $report, User $user): bool
{
return $report->getTeamId() !== $user->getTeamId()
|| $report->getAttribute('created_by') !== $user->getId();
}
public function getFormData(Request $request, ?string $uuid = null): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $uuid ? $this->automatedReportsService->getReport($uuid) : null;
return new JsonResponse(
$this->automatedReportsService->getAskJiminnyReportFormData($user, $report)
);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report form data', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch form data'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Create a new Ask Jiminny report.
*/
public function create(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$data = $this->automatedReportsService->createAskJiminnyReport($request->all(), $user);
return new JsonResponse($data);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to create Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to create report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Update an existing Ask Jiminny report.
*/
public function update(Request $request, string $uuid): JsonResponse
{
\Illuminate\Support\Facades\Log::channel('custom_channel')->info("UPDATE");
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReport($report, $request->all(), $user);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (InvalidArgumentException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Throwable $e) {
$this->logger->error('Failed to update Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to update report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Toggle Ask Jiminny report status (enable/disable).
*/
public function toggleStatus(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$data = $this->automatedReportsService->updateAskJiminnyReportStatus(
$report,
(bool) $request->input('enabled'),
);
return new JsonResponse($data);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to toggle Ask Jiminny report status', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to toggle report status'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* List all Ask Jiminny reports.
*/
public function list(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$sortColumn = $request->input('sort_column', 'created_at');
$sortDirection = $request->input('sort_direction', 'desc');
$data = $this->automatedReportsService->listAskJiminnyReports($user, $sortColumn, $sortDirection);
return new JsonResponse($data);
} catch (Throwable $e) {
$this->logger->error('Failed to list Ask Jiminny reports', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch reports'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Get a single Ask Jiminny report.
*/
public function get(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
return new JsonResponse($this->automatedReportsService->get($uuid));
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to get Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to fetch report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getReportsCount(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$resultsCount = $this->automatedReportsService->getReportResults($report)->count();
return new JsonResponse(['count' => $resultsCount]);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to count report results', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'report_uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to count report results'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
/**
* Delete an Ask Jiminny report.
*/
public function delete(Request $request, string $uuid): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
if ($request->boolean('delete_generated_reports')) {
$this->automatedReportsService->deleteReportResults($uuid);
}
$report = $this->automatedReportsService->getReport($uuid);
if ($this->isNotOwnedByUser($report, $user)) {
return new JsonResponse(['error' => 'Report not found'], Response::HTTP_NOT_FOUND);
}
$this->automatedReportsService->delete($uuid);
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (ModelNotFoundException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_NOT_FOUND);
} catch (Throwable $e) {
$this->logger->error('Failed to delete Ask Jiminny report', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
'uuid' => $uuid,
]);
return new JsonResponse(
['error' => 'Failed to delete report'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
public function getFilters(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
try {
$filters = $this->automatedReportsService->getAskJiminnyReportFilters($user);
return new JsonResponse(['filters' => $filters]);
} catch (Throwable $e) {
$this->logger->error('Failed to fetch Ask Jiminny report filters', [
'error' => $e->getMessage(),
'user_id' => $user->getId(),
]);
return new JsonResponse(
['error' => 'Failed to fetch filters'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
[2026-04-20 09:25:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:09] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6a7007ae-7e30-40d3-a9c9-a2ba6ebb4654","trace_id":"df1421c1-9742-4cab-9136-3b91fef3a225"}
[2026-04-20 09:25:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6a7007ae-7e30-40d3-a9c9-a2ba6ebb4654","trace_id":"df1421c1-9742-4cab-9136-3b91fef3a225"}
[2026-04-20 09:25:21] local.NOTICE: Monitoring start {"correlation_id":"b706ba8d-055b-4160-80ad-68f7db04afcb","trace_id":"0b7a1130-21d3-4aba-b7ec-c65d2c0f65b6"}
[2026-04-20 09:25:21] local.NOTICE: Monitoring end {"correlation_id":"b706ba8d-055b-4160-80ad-68f7db04afcb","trace_id":"0b7a1130-21d3-4aba-b7ec-c65d2c0f65b6"}
[2026-04-20 09:25:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"56e73e32-72f0-4918-8a84-53682b5b7df4","trace_id":"842ef1af-6992-4c51-9eca-402784f14ebf"}
[2026-04-20 09:25:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"56e73e32-72f0-4918-8a84-53682b5b7df4","trace_id":"842ef1af-6992-4c51-9eca-402784f14ebf"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
54952
|
|
67646
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Jiminny\Traits\RequiresUUID;
/**
* Jiminny\Models\AutomatedReportResult
*
* @property int $id
* @property string $uuid
* @property int $report_id
* @property string|null $name
* @property int $status
* @property int $reason
* @property string $media_type
* @property int|null $parent_id
* @property array|null $payload
* @property array|null $response
* @property Carbon|null $requested_at
* @property Carbon|null $generated_at
* @property Carbon|null $sent_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read \Jiminny\Models\AutomatedReport $report
* @property-read AutomatedReportResult|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, AutomatedReportResult> $children
*/
class AutomatedReportResult extends Model
{
use RequiresUUID;
/**
* Status constants
*/
public const int STATUS_DEFAULT = 0;
public const int STATUS_REQUESTED = 1;
public const int STATUS_GENERATED = 2;
public const int STATUS_SENT = 3;
public const int STATUS_FAILED = 4;
/**
* Reason constants
*/
public const int REASON_DEFAULT = 0;
public const int REASON_NOT_ENOUGH_ACTIVITIES = 1;
public const int REASON_PROPHET_API_ERROR = 2;
protected $table = 'automated_report_results';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'report_id',
'name',
'status',
'reason',
'media_type',
'parent_id',
'payload',
'response',
'requested_at',
'generated_at',
'sent_at',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'payload' => 'array',
'response' => 'array',
'requested_at' => 'datetime',
'generated_at' => 'datetime',
'sent_at' => 'datetime',
];
}
/**
* Get the automated report that owns this result.
*
* @return BelongsTo
*/
public function report(): BelongsTo
{
return $this->belongsTo(AutomatedReport::class, 'report_id')->withTrashed();
}
/**
* Get the parent report result.
*
* @return BelongsTo
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* Get the child report results.
*
* @return HasMany
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the ID of the automated report result.
*
* @return int
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/**
* Get the UUID of the automated report result.
*
* @return string
*/
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
/**
* Get the report ID of the automated report result.
*
* @return int
*/
public function getReportId(): int
{
return $this->getAttribute('report_id');
}
/**
* Get the name of the automated report result.
*
* @return ?string
*/
public function getName(): ?string
{
return $this->getAttribute('name');
}
/**
* Get the status of the automated report result.
*
* @return int
*/
public function getStatus(): int
{
return $this->getAttribute('status');
}
/**
* Get the reason of the automated report result.
*
* @return int
*/
public function getReason(): int
{
return $this->getAttribute('reason');
}
/**
* Get the media type of the automated report result.
*
* @return string
*/
public function getMediaType(): ?string
{
return $this->getAttribute('media_type');
}
/**
* Get the parent ID of the automated report result.
*
* @return int|null
*/
public function getParentId(): ?int
{
return $this->getAttribute('parent_id');
}
/**
* Get the payload of the automated report result.
*
* @return array|null
*/
public function getPayload(): ?array
{
return $this->getAttribute('payload');
}
/**
* Get the response of the automated report result.
*
* @return array|null
*/
public function getResponse(): ?array
{
return $this->getAttribute('response');
}
/**
* Get the requested at date of the automated report result.
*
* @return Carbon|null
*/
public function getRequestedAt(): ?Carbon
{
return $this->getAttribute('requested_at');
}
/**
* Get the generated at date of the automated report result.
*
* @return Carbon|null
*/
public function getGeneratedAt(): ?Carbon
{
return $this->getAttribute('generated_at');
}
/**
* Get the sent at date of the automated report result.
*
* @return Carbon|null
*/
public function getSentAt(): ?Carbon
{
return $this->getAttribute('sent_at');
}
/**
* Get the created at date of the automated report result.
*
* @return Carbon
*/
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
/**
* Get the updated at date of the automated report result.
*
* @return Carbon
*/
public function getUpdatedAt(): Carbon
{
return $this->getAttribute('updated_at');
}
/**
* Check if the report result is in requested status.
*
* @return bool
*/
public function isRequested(): bool
{
return $this->getStatus() === self::STATUS_REQUESTED;
}
/**
* Check if the report result is in generated status.
*
* @return bool
*/
public function isGenerated(): bool
{
return $this->getStatus() === self::STATUS_GENERATED;
}
/**
* Check if the report result is in sent status.
*
* @return bool
*/
public function isSent(): bool
{
return $this->getStatus() === self::STATUS_SENT;
}
/**
* Check if the report result is in failed status.
*
* @return bool
*/
public function isFailed(): bool
{
return $this->getStatus() === self::STATUS_FAILED;
}
public function getStatusLabel(): string
{
return match ($this->getStatus()) {
self::STATUS_REQUESTED => 'Requested',
self::STATUS_GENERATED => 'Generated',
self::STATUS_SENT => 'Sent',
self::STATUS_FAILED => 'Failed',
default => 'Default',
};
}
public function getReport(): AutomatedReport
{
return $this->getAttribute('report');
}
public function getFromDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['from_date'])) {
return null;
}
return Carbon::parse($payload['from_date']);
}
public function getToDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['to_date'])) {
return null;
}
return Carbon::parse($payload['to_date']);
}
public function getReportType(): ?string
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['report_type'])) {
return null;
}
return $payload['report_type'];
}
public function getGroups(): array
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['group_ids'])) {
return [];
}
return $payload['group_ids'];
}
public function getPdfUrl(): ?string
{
$response = $this->getResponse();
return $response['pdf_url'] ?? null;
}
public function getPodcastAudioUrl(): ?string
{
$response = $this->getResponse();
return $response['podcast_audio_url'] ?? null;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1
Previous Highlighted Error
Next Highlighted Error
[2026-04-20 09:25:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:09] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d1aac6bc-6000-46c5-8f94-0efb4f04038c","trace_id":"76738abf-c9e7-46d4-92d5-b01843c65288"}
[2026-04-20 09:25:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6a7007ae-7e30-40d3-a9c9-a2ba6ebb4654","trace_id":"df1421c1-9742-4cab-9136-3b91fef3a225"}
[2026-04-20 09:25:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6a7007ae-7e30-40d3-a9c9-a2ba6ebb4654","trace_id":"df1421c1-9742-4cab-9136-3b91fef3a225"}
[2026-04-20 09:25:21] local.NOTICE: Monitoring start {"correlation_id":"b706ba8d-055b-4160-80ad-68f7db04afcb","trace_id":"0b7a1130-21d3-4aba-b7ec-c65d2c0f65b6"}
[2026-04-20 09:25:21] local.NOTICE: Monitoring end {"correlation_id":"b706ba8d-055b-4160-80ad-68f7db04afcb","trace_id":"0b7a1130-21d3-4aba-b7ec-c65d2c0f65b6"}
[2026-04-20 09:25:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"56e73e32-72f0-4918-8a84-53682b5b7df4","trace_id":"842ef1af-6992-4c51-9eca-402784f14ebf"}
[2026-04-20 09:25:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"56e73e32-72f0-4918-8a84-53682b5b7df4","trace_id":"842ef1af-6992-4c51-9eca-402784f14ebf"}
[2026-04-20 09:25:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"cf673cfc-b1e6-49cd-9ec5-cef9891433e9","trace_id":"6af026e0-6a3c-4bb9-b494-aab268ea638c"}
[2026-04-20 09:25:34] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"cf673cfc-b1e6-49cd-9ec5-cef9891433e9","trace_id":"6af026e0-6a3c-4bb9-b494-aab268ea638c"}
[2026-04-20 09:25:35] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"cf673cfc-b1e6-49cd-9ec5-cef9891433e9","trace_id":"6af026e0-6a3c-4bb9-b494-aab268ea638c"}
[2026-04-20 09:25:35] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"cf673cfc-b1e6-49cd-9ec5-cef9891433e9","trace_id":"6af026e0-6a3c-4bb9-b494-aab268ea638c"}
[2026-04-20 09:25:39] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8d2c9252-4caa-4aa6-8526-6b168446925e","trace_id":"459d9614-5e90-4e06-b5b2-76e349aeb8f2"}
[2026-04-20 09:25:39] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8d2c9252-4caa-4aa6-8526-6b168446925e","trace_id":"459d9614-5e90-4e06-b5b2-76e349aeb8f2"}
[2026-04-20 09:25:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"069026eb-9ad3-47a1-91d8-c44c80218bb2","trace_id":"427c0738-0c80-40ee-8eb4-393526780dcf"}
[2026-04-20 09:25:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"069026eb-9ad3-47a1-91d8-c44c80218bb2","trace_id":"427c0738-0c80-40ee-8eb4-393526780dcf"}
[2026-04-20 09:25:49] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"5bc77933-1bc1-4f9a-b27f-be3714056b82","trace_id":"143765fb-3410-41c9-9ae6-90726e44ef00"}
[2026-04-20 09:25:49] local.INFO: Running pre-meeting notification command {"correlation_id":"5bc77933-1bc1-4f9a-b27f-be3714056b82","trace_id":"143765fb-3410-41c9-9ae6-90726e44ef00"}
[2026-04-20 09:25:49] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"5bc77933-1bc1-4f9a-b27f-be3714056b82","trace_id":"143765fb-3410-41c9-9ae6-90726e44ef00"}
[2026-04-20 09:25:53] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"91cfa967-2e79-41da-92e6-f04353cd5144","trace_id":"41959cde-0f13-420c-899a-86eaea3d3e35"}
[2026-04-20 09:25:53] local.INFO: Running conference:monitor:start command for activities in (2026-04-20 09:15:00, 2026-04-20 09:20:00] {"correlation_id":"91cfa967-2e79-41da-92e6-f04353cd5144","trace_id":"41959cde-0f13-420c-899a-86eaea3d3e35"}
[2026-04-20 09:25:53] local.INFO: [conference:monitor:start] No activities found in (2026-04-20 09:15:00, 2026-04-20 09:20:00] {"correlation_id":"91cfa967-2e79-41da-92e6-f04353cd5144","trace_id":"41959cde-0f13-420c-899a-86eaea3d3e35"}
[2026-04-20 09:25:53] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"91cfa967-2e79-41da-92e6-f04353cd5144","trace_id":"41959cde-0f13-420c-899a-86eaea3d3e35"}
[2026-04-20 09:25:58] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"74699d30-5655-4839-8916-9eec873aa45a","trace_id":"c5b0c7ed-c6a0-43be-bae7-6e7228f692af"}
[2026-04-20 09:25:58] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"09:20","to":"09:25"} {"correlation_id":"74699d30-5655-4839-8916-9eec873aa45a","trace_id":"c5b0c7ed-c6a0-43be-bae7-6e7228f692af"}
[2026-04-20 09:25:58] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"23:15","to":"23:20"} {"correlation_id":"74699d30-5655-4839-8916-9eec873aa45a","trace_id":"c5b0c7ed-c6a0-43be-bae7-6e7228f692af"}
[2026-04-20 09:25:58] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"74699d30-5655-4839-8916-9eec873aa45a","trace_id":"c5b0c7ed-c6a0-43be-bae7-6e7228f692af"}
[2026-04-20 09:26:04] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:04] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:05] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:05] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:05] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:05] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:05] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:05] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"55ca1c5c-e8f0-44d6-af86-d9debd61c9d8","trace_id":"2a2eb43c-6512-4120-8d05-8057da80d0f4"}
[2026-04-20 09:26:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"fbefdafa-09f0-44b0-b265-57d594e56fba","trace_id":"57c82917-0c5c-465e-9312-13e514443c0a"}
[2026-04-20 09:26:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"97247b04-8e86-4aab-9ad7-84e588dc9283","trace_id":"e74bd800-3958-4af1-a7f9-b8da7e70fa68"}
[2026-04-20 09:26:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"fbefdafa-09f0-44b0-b265-57d594e56fba","trace_id":"57c82917-0c5c-465e-9312-13e514443c0a"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":60.0} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-20T09:28:12.386968Z"} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Auth] Requesting new client credentials token {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"97247b04-8e86-4aab-9ad7-84e588dc9283","trace_id":"e74bd800-3958-4af1-a7f9-b8da7e70fa68"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Auth] Successfully obtained new access token {"expires_in":1800,"cached_for":1500} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:12] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:17] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:17] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:18] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:23] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:23] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:23] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:38] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:38] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:26:38] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"00ae31ed-358d-4db6-ae74-872a8fb8bc5a","trace_id":"e262e140-3ee0-4b2b-9696-9e9f728164f5"}
[2026-04-20 09:27:08] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":56,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":232.2,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.72} {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"a227fdaf-45a8-4f92-a341-7267c26312dc","trace_id":"ff115b4b-844c-4b5f-8925-a0d80a1dc35e"}
[2026-04-20 09:27:08] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"00ae31ed-358d-4db6-ae74-872a8fb8bc5a","trace_id":"e262e140-3ee0-4b2b-9696-9e9f728164f5"}
[2026-04-20 09:27:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"00ae31ed-358d-4db6-ae74-872a8fb8bc5a","trace_id":"e262e140-3ee0-4b2b-9696-9e9f728164f5"}
[2026-04-20 09:27:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"0aad6422-db8f-421e-a3b7-342632100eb7","trace_id":"dc52927b-8032-4bf0-8879-142b79dd7b09"}
[2026-04-20 09:27:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"0aad6422-db8f-421e-a3b7-342632100eb7","trace_id":"dc52927b-8032-4bf0-8879-142b79dd7b09"}
[2026-04-20 09:27:20] local.NOTICE: Monitoring start {"correlation_id":"d415c6f1-6e4a-4163-abe6-b819aa7e6afb","trace_id":"cab138a3-3a2c-49c0-b0a8-4499c79dbd71"}
[2026-04-20 09:27:20] local.NOTICE: Monitoring end {"correlation_id":"d415c6f1-6e4a-4163-abe6-b819aa7e6afb","trace_id":"cab138a3-3a2c-49c0-b0a8-4499c79dbd71"}
[2026-04-20 09:27:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6c500d95-7da9-4db0-81d3-3e152c0f5573","trace_id":"cb737bf4-defa-46b1-ace4-58fa716cf681"}
[2026-04-20 09:27:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6c500d95-7da9-4db0-81d3-3e152c0f5573","trace_id":"cb737bf4-defa-46b1-ace4-58fa716cf681"}
[2026-04-20 09:27:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"dd96fc62-6230-4eb8-bbad-159ec867d885","trace_id":"bb44cb63-8f3a-4ff2-a84c-c528a9c9b1e4"}
[2026-04-20 09:27:26] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"dd96fc62-6230-4eb8-bbad-159ec867d885","trace_id":"bb44cb63-8f3a-4ff2-a84c-c528a9c9b1e4"}
[2026-04-20 09:27:26] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"dd96fc62-6230-4eb8-bbad-159ec867d885","trace_id":"bb44cb63-8f3a-4ff2-a84c-c528a9c9b1e4"}
[2026-04-20 09:27:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"dd96fc62-6230-4eb8-bbad-159ec867d885","trace_id":"bb44cb63-8f3a-4ff2-a84c-c528a9c9b1e4"}
[2026-04-20 09:27:31] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9b8cdd15-d4c3-4b79-bae5-2b98d9541089","trace_id":"a0351ef0-170a-42ef-b534-f97aea8f053e"}
[2026-04-20 09:27:31] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"9b8cdd15-d4c3-4b79-bae5-2b98d9541089","trace_id":"a0351ef0-170a-42ef-b534-f97aea8f053e"}
[2026-04-20 09:27:31] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"9b8cdd15-d4c3-4b79-bae5-2b98d9541089","trace_id":"a0351ef0-170a-42ef-b534-f97aea8f053e"}
[2026-04-20 09:27:31] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9b8cdd15-d4c3-4b79-bae5-2b98d9541089","trace_id":"a0351ef0-170a-42ef-b534-f97aea8f053e"}
[2026-04-20 09:27:33] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 1 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"a61f2e55-8e1c-431e-b53e-8a3a375888d5","trace_id":"a0351ef0-170a-42ef-b534-f97aea8f053e"}
[2026-04-20 09:28:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"255bc036-33da-4733-93cf-e212b52368af","trace_id":"66b0d711-cfa5-4500-ac57-efc10a309079"}
[2026-04-20 09:28:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"255bc036-33da-4733-93cf-e212b52368af","trace_id":"66b0d711-cfa5-4500-ac57-efc10a309079"}
[2026-04-20 09:28:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"255bc036-33da-4733-93cf-e212b52368af","trace_id":"66b0d711-cfa5-4500-ac57-efc10a309079"}
[2026-04-20 09:28:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e6ed401d-f518-461c-879f-7f5657fb97f7","trace_id":"6d278154-4630-49ef-a5f1-b190f5ea3bae"}
[2026-04-20 09:28:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e6ed401d-f518-461c-879f-7f5657fb97f7","trace_id":"6d278154-4630-49ef-a5f1-b190f5ea3bae"}
[2026-04-20 09:28:07] local.NOTICE: Monitoring start {"correlation_id":"d2e92c79-2bae-4e71-8a60-a8fea677efc3","trace_id":"35531ee9-4f3b-45d1-8a16-4c47a8ea834a"}
[2026-04-20 09:28:07] local.NOTICE: Monitoring end {"correlation_id":"d2e92c79-2bae-4e71-8a60-a8fea677efc3","trace_id":"35531ee9-4f3b-45d1-8a16-4c47a8ea834a"}
[2026-04-20 09:28:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"eab5fa4b-3b27-4210-9800-22b233c2a0ec","trace_id":"8df16a06-c5e6-4811-a146-c7dd67293987"}
[2026-04-20 09:28:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"eab5fa4b-3b27-4210-9800-22b233c2a0ec","trace_id":"8df16a06-c5e6-4811-a146-c7dd67293987"}
[2026-04-20 09:28:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a218f56c-8890-4694-8706-69b3957c5f36","trace_id":"e2488098-5f90-4e6a-ab8f-b7d36f971e13"}
[2026-04-20 09:28:11] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"a218f56c-8890-4694-8706-69b3957c5f36","trace_id":"e2488098-5f90-4e6a-ab8f-b7d36f971e13"}
[2026-04-20 09:28:11] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"a218f56c-8890-4694-8706-69b3957c5f36","trace_id":"e2488098-5f90-4e6a-ab8f-b7d36f971e13"}
[2026-04-20 09:28:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a218f56c-8890-4694-8706-69b3957c5f36","trace_id":"e2488098-5f90-4e6a-ab8f-b7d36f971e13"}
[2026-04-20 09:28:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2d5134fb-2e19-4be7-9c68-8d4d1386a9ed","trace_id":"2767d4c0-0080-4e51-9a69-0d950ba794da"}
[2026-04-20 09:28:13] local.INFO: Running conference:monitor:count command for activities in (2026-04-20 09:26:00, 2026-04-20 09:28:00] {"correlation_id":"2d5134fb-2e19-4be7-9c68-8d4d1386a9ed","trace_id":"2767d4c0-0080-4e51-9a69-0d950ba794da"}
[2026-04-20 09:28:13] local.INFO: [conference:monitor:count] No activities found in (2026-04-20 09:26:00, 2026-04-20 09:28:00] {"correlation_id":"2d5134fb-2e19-4be7-9c68-8d4d1386a9ed","trace_id":"2767d4c0-0080-4e51-9a69-0d950ba794da"}
[2026-04-20 09:28:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2d5134fb-2e19-4be7-9c68-8d4d1386a9ed","trace_id":"2767d4c0-0080-4e51-9a69-0d950ba794da"}
[2026-04-20 09:28:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"737ded8b-5416-4f3b-be5a-5df46b0fa649","trace_id":"f8c83d08-ebe6-4641-b317-8e10be21882e"}
[2026-04-20 09:28:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"calendar:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:20] local.NOTICE: Calendar sync start {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"737ded8b-5416-4f3b-be5a-5df46b0fa649","trace_id":"f8c83d08-ebe6-4641-b317-8e10be21882e"}
[2026-04-20 09:28:20] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1393,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:20] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1393,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:20] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:20] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1393,"provider":"google","refreshToken":"5aa7e2d96b53201cd16fca5d2e4ef3ad03320971fc064781d18aee3ae7b99fbf","state":"full-refresh"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1393,"provider":"google","responseBody":{"error":"invalid_grant","error_description":"Account has been deleted"}} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1393,"provider":"google","reason":"Flow refresh required."} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1387,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1387,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1387,"provider":"google","refreshToken":"8157ac6de94842937194009e9c50e459253600f799dacf6a40755ffdbeb5bba6","state":"full-refresh"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1387,"provider":"google","responseBody":{"error":"invalid_grant","error_description":"Account has been deleted"}} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1387,"provider":"google","reason":"Flow refresh required."} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1348,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1348,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1348,"provider":"google","refreshToken":"9e7d13d3032d0cb1b79d8e95aef01383e8e91eb52ff8ee960c8a0b6b95cd8c73","state":"full-refresh"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1348,"provider":"google","responseBody":{"error":"invalid_grant","error_description":"Bad Request"}} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1348,"provider":"google","reason":"Flow refresh required."} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1361,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1361,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:21] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1361,"provider":"google","refreshToken":"6c843da199c2b9907445329304fcc4ec5057a4ee748d8299641764395c08e1fd","state":"full-refresh"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1361,"provider":"google","responseBody":{"error":"invalid_grant","error_description":"Account has been deleted"}} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1361,"provider":"google","reason":"Flow refresh required."} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1310,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1310,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1310,"provider":"google","refreshToken":"e34818922c2830a660813a63f6169a4a9a992ae2cccd7dc8dd7796cfdb470ef1","state":"full-refresh"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1310,"provider":"google","responseBody":{"error":"invalid_grant","error_description":"Bad Request"}} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1310,"provider":"google","reason":"Flow refresh required."} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1333,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1333,"provider":"google"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1333,"provider":"google","refreshToken":"6c902986546d8e8da1dc539b046cdc1d458f519acc972e5b5f1d6a1a295165e0","state":"full-refresh"} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1333,"provider":"google","responseBody":{"error":"unauthorized_client","error_description":"Unauthorized"}} {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f692a7b4b"}
[2026-04-20 09:28:22] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c","trace_id":"31b451e0-b4d6-4dd6-89fe-af7f...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
67646
|
|
67647
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
PhostormVIewINavigarecodeLaravelTOOISWindowmelpFV faVsco.js( #11894 on JY-18909-automated-reports-aslProiect v(C) Service.php© Field.phpC FieldRepository-pnp(c)Ask.liminnvReportAcuvilyselvice.onp= custom.logkeporcontroller.pnpAutomateakeportssendcommana.ong© AutomatedReportsCommand.phpAutomatedReportsService.php(c) AutomatedReportsServicelest.onp= nuospor-lournal-poll.log© CreateActivityLoggedEvent.php© ActivityLogged.php<)> phpunit.xmos ttt.jsE oauth-private.keyE oauth-public.kevE storageE supervisord.pidRequestGenerateAskJiminnyreportJob.png) sendkeportwaillJob.phpC) Requ© RequestGeneratereportJob.png© AutomatedReportResult.php x ©AutomatedReport.phpwtext-relav.isonv w tests> Featurelintearation• Servicesv UniActionsN ComoonentN ConfiaurationiM ConcoleiM ContractsM DomainD EnumsD EventsD ExceptionsM fivturosD GuardsServicesv M DatabaseV AEUA console 3 sv A liminnv@localhostA HS_localv # pRODA console 1 s 620 ms# STAGING# console 1 s 290 mg→ Nockorclass AutomatedReportResult extends Model* Status constants8lusadedpublic const int STATUS_DEFAULT = 0;o usagespublic const int STATUS_REQUESTED = 1;lo usagespublic const int STATUS_GENERATED = 2;public const int STATUS_SENT = 3;public const int STATUS_FAILED = 4;* Reason constants6 usagespublic const int REASON DEFAULT = 0:public const int REASON_NOT ENOUGH ACTIVITIES = 1public const int REASON PROPHET APT ERROR = 2.OutputHiii liminnv ucerc Xiii 2 rows vdidyg00+-5@4 TxAutovЦ Ae÷ uuid (UUID with time-low a….T+ !name Temainy! secondary_email Y÷@status 7 ÷password Y22245 56c2c454-1af3-4f80-b1cd-cf3778478f71Jiminny Web SA 2jiminny_web_sa2_DELETED@jiminny.com2 <null>23547 eaa39d4a-5b51-4e76-a3ca-9ea4371ab108Web Service Account 2jiminny_web_sa2@jiminny.com1 <null>100% S2Tue 21 Apr 18:53:28A8V1V1Ain nememhen token= custom.log= laravel.log XA SF fliminnv@localhost)A HS_local (jiminny@localhost]console [PRODconsole (Fulreport-not-generated.blade.phpPenortNotGeneratedMail.loh.nhn# concole [STAGING1& The file size (8.45 MB) exceeds the configured limit (2.56 MB). Code insight features are not available12026-04-20 09:28:25 Z000INF0: EncrvotedTokenManager Generating access token. "mod12026-04-20 09/28/251 1OC0NEORSociaL AccountService Refreshing token from orovidern "social Accountd":1540, "опомлин12026-04-20 09:28:24] Local.ERROR[SocialAccountServicel Failed to refresh token {"socialAccountId".1370, "nroviden"."office" "nes2026-04-20 09:28:241 10c01TNF0• [SocialAccount0bserver Savina model"correlation id":"6cf508c6-c916-4870-ade7-437083ah909c")181182183193195196197[2026-04-20 09:28:24] local.ERROR: [SocialAccountService] Failed to refresh token {"socialAccountId":1370,"provider":"office","rea2026-04-20 09-28-241 10001INEO• Social AccountServiicel Fetchind token "social AccountId" • 1202 "ncoyaden" • "office" "connelat io[2026-04-20 09:28:24] Local.INF0: [SocialAccountService] Token needs refreshing {"socialAccountId":1202, "provider":"office"} {"cor[2026-04-20 09:28:24] local.INF0: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"6cf508c6-c)[2026-04-20 09:28:24] local.INF0: [SocialAçcountService] Refreshing token from provider {"socialAccountId":1202,"provider":"office[2026-04-20 09:28:25] local.ERROR:[SocialAccountService] Failed to refresh token {"socialAccountId":1202,"provider":"office", "res[2026-04-20 09:28:25] local.INF0: [SocialAccount0bserver] Saving model {"correlation_id":"6cf508c6-c916-4870-ade7-437083ab909c" ,"[2026-04-20 09:28:25] local.ERROR:ҐsocialAccoun+Sonvicol Cailed to nofnoch tokon Sicocialdccounttdl.1202 innovidonl."officoll ilnos[2026-04-20 09:28:25] local.INF0: [SocialAccountServicel Fetching token {"socialAccountId":1502,"provider":"google"} {"correlation[2026-04-20 09:28:25] local.INF0: [SocialAccountServicel Token retrieved {"socialAccountId":1502,"provider":"google"} {"correlatio[2026-04-20 09:28:25] local.INF0: [EncryptedTokenManager] Generating access token. {"mode":"Zeqacy"} {"correlation id":"6cf508c6-c)[2026-04-20 09:28:25] local.INF0: Calendar sync job dispatched {"calendar id":501} {"correlation id":"6cf508c6-c916-4870-ade7-4370;[2026-04-20 09:28:25] local.INF0: [SocialAccountServicel Fetching token {"socialAccountId":1300, "provider":"google"} {"correlation[2026-04-20 09:28:25] local.INF0: [SocialAccountServicel Token needs refreshing {"socialAccountId":1300, "providen"- "gooale"? {"con[2026-04-20 09:28:251 local.INF0: [EncryptedTokenManager] Generating access token. {"mode". "legacy"? {"correlation_id"."6cf508c6-o[2026-04-20 09:28:251 local.INF0: [SocialAccountServicel Refreshing token from provider {"socialAccountId": 1300, "provider" . "gooqle[2026-04-20 09:28:251 local.ERRORSocialAccountServicel Farled to refresh token "socialAccountld": 1500."providen":"aooqle"."res[2026-04-20 09:28:251 local.INF0: [SocialAccountObserverl Saving model {"correlation id"."6cf508c6-c916-4870-ade7-437083ab909c"."2826-04-20 09:28:25 Local. ERROR:SocialAccountServicel Farled to refresh token ""socialAccountld":1500."providen":"aooqle"."rea[2026-04-20 09:28:25] LocaL. INFO: [SocialAccountServäce] Fetching token ("sociaLAccountId":1409, "provider": "g009le"} ("correlationcsvmuses twn facton authDphoto_path Y/5f0f4810-7e77-4086-8f69-93429ae4d70b/avatars/56c2c454-1af3-4f80-b1cd-cf3778478f71.png/Sf0f4810-7e77-4086-8f69-93429ae4d70b/avatars/eaa39d4a-5b51-4e76-a3ca-9ea4371ab108.pngMauthAlenulls0enulsW Windsurf Teams 2:1 UTF-8 P 4 spaces...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
67647
|
|
67651
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Jiminny\Traits\RequiresUUID;
/**
* Jiminny\Models\AutomatedReportResult
*
* @property int $id
* @property string $uuid
* @property int $report_id
* @property string|null $name
* @property int $status
* @property int $reason
* @property string $media_type
* @property int|null $parent_id
* @property array|null $payload
* @property array|null $response
* @property Carbon|null $requested_at
* @property Carbon|null $generated_at
* @property Carbon|null $sent_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read \Jiminny\Models\AutomatedReport $report
* @property-read AutomatedReportResult|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, AutomatedReportResult> $children
*/
class AutomatedReportResult extends Model
{
use RequiresUUID;
/**
* Status constants
*/
public const int STATUS_DEFAULT = 0;
public const int STATUS_REQUESTED = 1;
public const int STATUS_GENERATED = 2;
public const int STATUS_SENT = 3;
public const int STATUS_FAILED = 4;
/**
* Reason constants
*/
public const int REASON_DEFAULT = 0;
public const int REASON_NOT_ENOUGH_ACTIVITIES = 1;
public const int REASON_PROPHET_API_ERROR = 2;
protected $table = 'automated_report_results';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'report_id',
'name',
'status',
'reason',
'media_type',
'parent_id',
'payload',
'response',
'requested_at',
'generated_at',
'sent_at',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'payload' => 'array',
'response' => 'array',
'requested_at' => 'datetime',
'generated_at' => 'datetime',
'sent_at' => 'datetime',
];
}
/**
* Get the automated report that owns this result.
*
* @return BelongsTo
*/
public function report(): BelongsTo
{
return $this->belongsTo(AutomatedReport::class, 'report_id')->withTrashed();
}
/**
* Get the parent report result.
*
* @return BelongsTo
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* Get the child report results.
*
* @return HasMany
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the ID of the automated report result.
*
* @return int
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/**
* Get the UUID of the automated report result.
*
* @return string
*/
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
/**
* Get the report ID of the automated report result.
*
* @return int
*/
public function getReportId(): int
{
return $this->getAttribute('report_id');
}
/**
* Get the name of the automated report result.
*
* @return ?string
*/
public function getName(): ?string
{
return $this->getAttribute('name');
}
/**
* Get the status of the automated report result.
*
* @return int
*/
public function getStatus(): int
{
return $this->getAttribute('status');
}
/**
* Get the reason of the automated report result.
*
* @return int
*/
public function getReason(): int
{
return $this->getAttribute('reason');
}
/**
* Get the media type of the automated report result.
*
* @return string
*/
public function getMediaType(): ?string
{
return $this->getAttribute('media_type');
}
/**
* Get the parent ID of the automated report result.
*
* @return int|null
*/
public function getParentId(): ?int
{
return $this->getAttribute('parent_id');
}
/**
* Get the payload of the automated report result.
*
* @return array|null
*/
public function getPayload(): ?array
{
return $this->getAttribute('payload');
}
/**
* Get the response of the automated report result.
*
* @return array|null
*/
public function getResponse(): ?array
{
return $this->getAttribute('response');
}
/**
* Get the requested at date of the automated report result.
*
* @return Carbon|null
*/
public function getRequestedAt(): ?Carbon
{
return $this->getAttribute('requested_at');
}
/**
* Get the generated at date of the automated report result.
*
* @return Carbon|null
*/
public function getGeneratedAt(): ?Carbon
{
return $this->getAttribute('generated_at');
}
/**
* Get the sent at date of the automated report result.
*
* @return Carbon|null
*/
public function getSentAt(): ?Carbon
{
return $this->getAttribute('sent_at');
}
/**
* Get the created at date of the automated report result.
*
* @return Carbon
*/
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
/**
* Get the updated at date of the automated report result.
*
* @return Carbon
*/
public function getUpdatedAt(): Carbon
{
return $this->getAttribute('updated_at');
}
/**
* Check if the report result is in requested status.
*
* @return bool
*/
public function isRequested(): bool
{
return $this->getStatus() === self::STATUS_REQUESTED;
}
/**
* Check if the report result is in generated status.
*
* @return bool
*/
public function isGenerated(): bool
{
return $this->getStatus() === self::STATUS_GENERATED;
}
/**
* Check if the report result is in sent status.
*
* @return bool
*/
public function isSent(): bool
{
return $this->getStatus() === self::STATUS_SENT;
}
/**
* Check if the report result is in failed status.
*
* @return bool
*/
public function isFailed(): bool
{
return $this->getStatus() === self::STATUS_FAILED;
}
public function getStatusLabel(): string
{
return match ($this->getStatus()) {
self::STATUS_REQUESTED => 'Requested',
self::STATUS_GENERATED => 'Generated',
self::STATUS_SENT => 'Sent',
self::STATUS_FAILED => 'Failed',
default => 'Default',
};
}
public function getReport(): AutomatedReport
{
return $this->getAttribute('report');
}
public function getFromDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['from_date'])) {
return null;
}
return Carbon::parse($payload['from_date']);
}
public function getToDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['to_date'])) {
return null;
}
return Carbon::parse($payload['to_date']);
}
public function getReportType(): ?string
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['report_type'])) {
return null;
}
return $payload['report_type'];
}
public function getGroups(): array
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['group_ids'])) {
return [];
}
return $payload['group_ids'];
}
public function getPdfUrl(): ?string
{
$response = $this->getResponse();
return $response['pdf_url'] ?? null;
}
public function getPodcastAudioUrl(): ?string
{
$response = $this->getResponse();
return $response['podcast_audio_url'] ?? null;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
67651
|
|
67652
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Jiminny\Traits\RequiresUUID;
/**
* Jiminny\Models\AutomatedReportResult
*
* @property int $id
* @property string $uuid
* @property int $report_id
* @property string|null $name
* @property int $status
* @property int $reason
* @property string $media_type
* @property int|null $parent_id
* @property array|null $payload
* @property array|null $response
* @property Carbon|null $requested_at
* @property Carbon|null $generated_at
* @property Carbon|null $sent_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read \Jiminny\Models\AutomatedReport $report
* @property-read AutomatedReportResult|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, AutomatedReportResult> $children
*/
class AutomatedReportResult extends Model
{
use RequiresUUID;
/**
* Status constants
*/
public const int STATUS_DEFAULT = 0;
public const int STATUS_REQUESTED = 1;
public const int STATUS_GENERATED = 2;
public const int STATUS_SENT = 3;
public const int STATUS_FAILED = 4;
/**
* Reason constants
*/
public const int REASON_DEFAULT = 0;
public const int REASON_NOT_ENOUGH_ACTIVITIES = 1;
public const int REASON_PROPHET_API_ERROR = 2;
protected $table = 'automated_report_results';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'report_id',
'name',
'status',
'reason',
'media_type',
'parent_id',
'payload',
'response',
'requested_at',
'generated_at',
'sent_at',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'payload' => 'array',
'response' => 'array',
'requested_at' => 'datetime',
'generated_at' => 'datetime',
'sent_at' => 'datetime',
];
}
/**
* Get the automated report that owns this result.
*
* @return BelongsTo
*/
public function report(): BelongsTo
{
return $this->belongsTo(AutomatedReport::class, 'report_id')->withTrashed();
}
/**
* Get the parent report result.
*
* @return BelongsTo
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* Get the child report results.
*
* @return HasMany
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the ID of the automated report result.
*
* @return int
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/**
* Get the UUID of the automated report result.
*
* @return string
*/
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
/**
* Get the report ID of the automated report result.
*
* @return int
*/
public function getReportId(): int
{
return $this->getAttribute('report_id');
}
/**
* Get the name of the automated report result.
*
* @return ?string
*/
public function getName(): ?string
{
return $this->getAttribute('name');
}
/**
* Get the status of the automated report result.
*
* @return int
*/
public function getStatus(): int
{
return $this->getAttribute('status');
}
/**
* Get the reason of the automated report result.
*
* @return int
*/
public function getReason(): int
{
return $this->getAttribute('reason');
}
/**
* Get the media type of the automated report result.
*
* @return string
*/
public function getMediaType(): ?string
{
return $this->getAttribute('media_type');
}
/**
* Get the parent ID of the automated report result.
*
* @return int|null
*/
public function getParentId(): ?int
{
return $this->getAttribute('parent_id');
}
/**
* Get the payload of the automated report result.
*
* @return array|null
*/
public function getPayload(): ?array
{
return $this->getAttribute('payload');
}
/**
* Get the response of the automated report result.
*
* @return array|null
*/
public function getResponse(): ?array
{
return $this->getAttribute('response');
}
/**
* Get the requested at date of the automated report result.
*
* @return Carbon|null
*/
public function getRequestedAt(): ?Carbon
{
return $this->getAttribute('requested_at');
}
/**
* Get the generated at date of the automated report result.
*
* @return Carbon|null
*/
public function getGeneratedAt(): ?Carbon
{
return $this->getAttribute('generated_at');
}
/**
* Get the sent at date of the automated report result.
*
* @return Carbon|null
*/
public function getSentAt(): ?Carbon
{
return $this->getAttribute('sent_at');
}
/**
* Get the created at date of the automated report result.
*
* @return Carbon
*/
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
/**
* Get the updated at date of the automated report result.
*
* @return Carbon
*/
public function getUpdatedAt(): Carbon
{
return $this->getAttribute('updated_at');
}
/**
* Check if the report result is in requested status.
*
* @return bool
*/
public function isRequested(): bool
{
return $this->getStatus() === self::STATUS_REQUESTED;
}
/**
* Check if the report result is in generated status.
*
* @return bool
*/
public function isGenerated(): bool
{
return $this->getStatus() === self::STATUS_GENERATED;
}
/**
* Check if the report result is in sent status.
*
* @return bool
*/
public function isSent(): bool
{
return $this->getStatus() === self::STATUS_SENT;
}
/**
* Check if the report result is in failed status.
*
* @return bool
*/
public function isFailed(): bool
{
return $this->getStatus() === self::STATUS_FAILED;
}
public function getStatusLabel(): string
{
return match ($this->getStatus()) {
self::STATUS_REQUESTED => 'Requested',
self::STATUS_GENERATED => 'Generated',
self::STATUS_SENT => 'Sent',
self::STATUS_FAILED => 'Failed',
default => 'Default',
};
}
public function getReport(): AutomatedReport
{
return $this->getAttribute('report');
}
public function getFromDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['from_date'])) {
return null;
}
return Carbon::parse($payload['from_date']);
}
public function getToDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['to_date'])) {
return null;
}
return Carbon::parse($payload['to_date']);
}
public function getReportType(): ?string
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['report_type'])) {
return null;
}
return $payload['report_type'];
}
public function getGroups(): array
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['group_ids'])) {
return [];
}
return $payload['group_ids'];
}
public function getPdfUrl(): ?string
{
$response = $this->getResponse();
return $response['pdf_url'] ?? null;
}
public function getPodcastAudioUrl(): ?string
{
$response = $this->getResponse();
return $response['podcast_audio_url'] ?? null;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
67652
|
|
67667
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Jiminny\Traits\RequiresUUID;
/**
* Jiminny\Models\AutomatedReportResult
*
* @property int $id
* @property string $uuid
* @property int $report_id
* @property string|null $name
* @property int $status
* @property int $reason
* @property string $media_type
* @property int|null $parent_id
* @property array|null $payload
* @property array|null $response
* @property Carbon|null $requested_at
* @property Carbon|null $generated_at
* @property Carbon|null $sent_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read \Jiminny\Models\AutomatedReport $report
* @property-read AutomatedReportResult|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, AutomatedReportResult> $children
*/
class AutomatedReportResult extends Model
{
use RequiresUUID;
/**
* Status constants
*/
public const int STATUS_DEFAULT = 0;
public const int STATUS_REQUESTED = 1;
public const int STATUS_GENERATED = 2;
public const int STATUS_SENT = 3;
public const int STATUS_FAILED = 4;
/**
* Reason constants
*/
public const int REASON_DEFAULT = 0;
public const int REASON_NOT_ENOUGH_ACTIVITIES = 1;
public const int REASON_PROPHET_API_ERROR = 2;
protected $table = 'automated_report_results';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'report_id',
'name',
'status',
'reason',
'media_type',
'parent_id',
'payload',
'response',
'requested_at',
'generated_at',
'sent_at',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'payload' => 'array',
'response' => 'array',
'requested_at' => 'datetime',
'generated_at' => 'datetime',
'sent_at' => 'datetime',
];
}
/**
* Get the automated report that owns this result.
*
* @return BelongsTo
*/
public function report(): BelongsTo
{
return $this->belongsTo(AutomatedReport::class, 'report_id')->withTrashed();
}
/**
* Get the parent report result.
*
* @return BelongsTo
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* Get the child report results.
*
* @return HasMany
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the ID of the automated report result.
*
* @return int
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/**
* Get the UUID of the automated report result.
*
* @return string
*/
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
/**
* Get the report ID of the automated report result.
*
* @return int
*/
public function getReportId(): int
{
return $this->getAttribute('report_id');
}
/**
* Get the name of the automated report result.
*
* @return ?string
*/
public function getName(): ?string
{
return $this->getAttribute('name');
}
/**
* Get the status of the automated report result.
*
* @return int
*/
public function getStatus(): int
{
return $this->getAttribute('status');
}
/**
* Get the reason of the automated report result.
*
* @return int
*/
public function getReason(): int
{
return $this->getAttribute('reason');
}
/**
* Get the media type of the automated report result.
*
* @return string
*/
public function getMediaType(): ?string
{
return $this->getAttribute('media_type');
}
/**
* Get the parent ID of the automated report result.
*
* @return int|null
*/
public function getParentId(): ?int
{
return $this->getAttribute('parent_id');
}
/**
* Get the payload of the automated report result.
*
* @return array|null
*/
public function getPayload(): ?array
{
return $this->getAttribute('payload');
}
/**
* Get the response of the automated report result.
*
* @return array|null
*/
public function getResponse(): ?array
{
return $this->getAttribute('response');
}
/**
* Get the requested at date of the automated report result.
*
* @return Carbon|null
*/
public function getRequestedAt(): ?Carbon
{
return $this->getAttribute('requested_at');
}
/**
* Get the generated at date of the automated report result.
*
* @return Carbon|null
*/
public function getGeneratedAt(): ?Carbon
{
return $this->getAttribute('generated_at');
}
/**
* Get the sent at date of the automated report result.
*
* @return Carbon|null
*/
public function getSentAt(): ?Carbon
{
return $this->getAttribute('sent_at');
}
/**
* Get the created at date of the automated report result.
*
* @return Carbon
*/
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
/**
* Get the updated at date of the automated report result.
*
* @return Carbon
*/
public function getUpdatedAt(): Carbon
{
return $this->getAttribute('updated_at');
}
/**
* Check if the report result is in requested status.
*
* @return bool
*/
public function isRequested(): bool
{
return $this->getStatus() === self::STATUS_REQUESTED;
}
/**
* Check if the report result is in generated status.
*
* @return bool
*/
public function isGenerated(): bool
{
return $this->getStatus() === self::STATUS_GENERATED;
}
/**
* Check if the report result is in sent status.
*
* @return bool
*/
public function isSent(): bool
{
return $this->getStatus() === self::STATUS_SENT;
}
/**
* Check if the report result is in failed status.
*
* @return bool
*/
public function isFailed(): bool
{
return $this->getStatus() === self::STATUS_FAILED;
}
public function getStatusLabel(): string
{
return match ($this->getStatus()) {
self::STATUS_REQUESTED => 'Requested',
self::STATUS_GENERATED => 'Generated',
self::STATUS_SENT => 'Sent',
self::STATUS_FAILED => 'Failed',
default => 'Default',
};
}
public function getReport(): AutomatedReport
{
return $this->getAttribute('report');
}
public function getFromDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['from_date'])) {
return null;
}
return Carbon::parse($payload['from_date']);
}
public function getToDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['to_date'])) {
return null;
}
return Carbon::parse($payload['to_date']);
}
public function getReportType(): ?string
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['report_type'])) {
return null;
}
return $payload['report_type'];
}
public function getGroups(): array
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['group_ids'])) {
return [];
}
return $payload['group_ids'];
}
public function getPdfUrl(): ?string
{
$response = $this->getResponse();
return $response['pdf_url'] ?? null;
}
public function getPodcastAudioUrl(): ?string
{
$response = $this->getResponse();
return $response['podcast_audio_url'] ?? null;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
67667
|
|
67668
|
Project: faVsco.js, menu
#11894 on JY-18909-automa Project: faVsco.js, menu
#11894 on JY-18909-automated-reports-ask-jiminny, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
8
1
1
Previous Highlighted Error
Next Highlighted Error
<?php
namespace Jiminny\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Jiminny\Traits\RequiresUUID;
/**
* Jiminny\Models\AutomatedReportResult
*
* @property int $id
* @property string $uuid
* @property int $report_id
* @property string|null $name
* @property int $status
* @property int $reason
* @property string $media_type
* @property int|null $parent_id
* @property array|null $payload
* @property array|null $response
* @property Carbon|null $requested_at
* @property Carbon|null $generated_at
* @property Carbon|null $sent_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read \Jiminny\Models\AutomatedReport $report
* @property-read AutomatedReportResult|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, AutomatedReportResult> $children
*/
class AutomatedReportResult extends Model
{
use RequiresUUID;
/**
* Status constants
*/
public const int STATUS_DEFAULT = 0;
public const int STATUS_REQUESTED = 1;
public const int STATUS_GENERATED = 2;
public const int STATUS_SENT = 3;
public const int STATUS_FAILED = 4;
/**
* Reason constants
*/
public const int REASON_DEFAULT = 0;
public const int REASON_NOT_ENOUGH_ACTIVITIES = 1;
public const int REASON_PROPHET_API_ERROR = 2;
protected $table = 'automated_report_results';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'report_id',
'name',
'status',
'reason',
'media_type',
'parent_id',
'payload',
'response',
'requested_at',
'generated_at',
'sent_at',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'payload' => 'array',
'response' => 'array',
'requested_at' => 'datetime',
'generated_at' => 'datetime',
'sent_at' => 'datetime',
];
}
/**
* Get the automated report that owns this result.
*
* @return BelongsTo
*/
public function report(): BelongsTo
{
return $this->belongsTo(AutomatedReport::class, 'report_id')->withTrashed();
}
/**
* Get the parent report result.
*
* @return BelongsTo
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* Get the child report results.
*
* @return HasMany
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the ID of the automated report result.
*
* @return int
*/
public function getId(): int
{
return $this->getAttribute('id');
}
/**
* Get the UUID of the automated report result.
*
* @return string
*/
public function getUuid(): string
{
return $this->getAttribute('id_string');
}
/**
* Get the report ID of the automated report result.
*
* @return int
*/
public function getReportId(): int
{
return $this->getAttribute('report_id');
}
/**
* Get the name of the automated report result.
*
* @return ?string
*/
public function getName(): ?string
{
return $this->getAttribute('name');
}
/**
* Get the status of the automated report result.
*
* @return int
*/
public function getStatus(): int
{
return $this->getAttribute('status');
}
/**
* Get the reason of the automated report result.
*
* @return int
*/
public function getReason(): int
{
return $this->getAttribute('reason');
}
/**
* Get the media type of the automated report result.
*
* @return string
*/
public function getMediaType(): ?string
{
return $this->getAttribute('media_type');
}
/**
* Get the parent ID of the automated report result.
*
* @return int|null
*/
public function getParentId(): ?int
{
return $this->getAttribute('parent_id');
}
/**
* Get the payload of the automated report result.
*
* @return array|null
*/
public function getPayload(): ?array
{
return $this->getAttribute('payload');
}
/**
* Get the response of the automated report result.
*
* @return array|null
*/
public function getResponse(): ?array
{
return $this->getAttribute('response');
}
/**
* Get the requested at date of the automated report result.
*
* @return Carbon|null
*/
public function getRequestedAt(): ?Carbon
{
return $this->getAttribute('requested_at');
}
/**
* Get the generated at date of the automated report result.
*
* @return Carbon|null
*/
public function getGeneratedAt(): ?Carbon
{
return $this->getAttribute('generated_at');
}
/**
* Get the sent at date of the automated report result.
*
* @return Carbon|null
*/
public function getSentAt(): ?Carbon
{
return $this->getAttribute('sent_at');
}
/**
* Get the created at date of the automated report result.
*
* @return Carbon
*/
public function getCreatedAt(): Carbon
{
return $this->getAttribute('created_at');
}
/**
* Get the updated at date of the automated report result.
*
* @return Carbon
*/
public function getUpdatedAt(): Carbon
{
return $this->getAttribute('updated_at');
}
/**
* Check if the report result is in requested status.
*
* @return bool
*/
public function isRequested(): bool
{
return $this->getStatus() === self::STATUS_REQUESTED;
}
/**
* Check if the report result is in generated status.
*
* @return bool
*/
public function isGenerated(): bool
{
return $this->getStatus() === self::STATUS_GENERATED;
}
/**
* Check if the report result is in sent status.
*
* @return bool
*/
public function isSent(): bool
{
return $this->getStatus() === self::STATUS_SENT;
}
/**
* Check if the report result is in failed status.
*
* @return bool
*/
public function isFailed(): bool
{
return $this->getStatus() === self::STATUS_FAILED;
}
public function getStatusLabel(): string
{
return match ($this->getStatus()) {
self::STATUS_REQUESTED => 'Requested',
self::STATUS_GENERATED => 'Generated',
self::STATUS_SENT => 'Sent',
self::STATUS_FAILED => 'Failed',
default => 'Default',
};
}
public function getReport(): AutomatedReport
{
return $this->getAttribute('report');
}
public function getFromDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['from_date'])) {
return null;
}
return Carbon::parse($payload['from_date']);
}
public function getToDate(): ?Carbon
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['to_date'])) {
return null;
}
return Carbon::parse($payload['to_date']);
}
public function getReportType(): ?string
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['report_type'])) {
return null;
}
return $payload['report_type'];
}
public function getGroups(): array
{
$payload = $this->getPayload();
if (empty($payload) || empty($payload['group_ids'])) {
return [];
}
return $payload['group_ids'];
}
public function getPdfUrl(): ?string
{
$response = $this->getResponse();
return $response['pdf_url'] ?? null;
}
public function getPodcastAudioUrl(): ?string
{
$response = $this->getResponse();
return $response['podcast_audio_url'] ?? null;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
67668
|
|
70709
|
Project: faVsco.js, menu
iTerm2ShellEditViewSessio Project: faVsco.js, menu
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpdockerDOCKER• 881dockerLast login:Tue Apr 2109:09:21 on ttys011882-zsh* Build full day ac... • &4Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~§ devroot@docker_lamp_1:/home/jiminny# php artisan automated-reports --report-id 71[automated-reports] Automated report found Not enpough activitiesroot@docker_lamp_1:/home/jiminny#screenpipe"Support Daily - in 47 mA100% <7O 85-zsh86APP (-zsh)87Wed 22 Apr 14:13:20T81ec2-user@ip-10-...• *8|+...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70709
|
|
70710
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
2
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Reports;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Support\Collection;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Team;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
class AutomatedReportsCommand extends Command
{
/**
* Log prefix for all log messages
*/
private const string LOG_PREFIX = '[automated-reports]';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'automated-reports
{--report-id= : Process a specific report by ID or UUID (bypasses frequency scheduling)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Process automated reports based on their frequency (weekly, monthly, quarterly).
Use --report-id to manually trigger a specific report by ID or UUID.';
public function __construct(
private readonly LoggerInterface $logger,
private readonly BusDispatcher $dispatcher,
private readonly AutomatedReportsRepository $reportRepository
) {
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
$this->logger->info(self::LOG_PREFIX . ' Started');
$this->disableExpiredAskJiminnyReports();
$now = Carbon::now();
$isMonday = $now->isMonday();
$isFirstDayOfMonth = $now->day === 1;
$currentMonth = $now->month;
// Check if the current month is a quarterly month (January, April, July, October)
$isQuarterlyMonth = in_array($currentMonth, [1, 4, 7, 10], true);
$this->logger->info(self::LOG_PREFIX . ' Checking conditions', [
'isMonday' => $isMonday,
'isFirstDayOfMonth' => $isFirstDayOfMonth,
'currentMonth' => $currentMonth,
'isQuarterlyMonth' => $isQuarterlyMonth,
]);
// Process daily reports
$this->processReports(AutomatedReportsService::FREQUENCY_DAILY);
// Process weekly reports on Mondays
if ($isMonday) {
$this->processReports(AutomatedReportsService::FREQUENCY_WEEKLY);
}
// Process monthly reports on the first day of the month
if ($isFirstDayOfMonth) {
$this->processReports(AutomatedReportsService::FREQUENCY_MONTHLY);
}
// Process quarterly reports on the first day of January, April, July, and October
if ($isFirstDayOfMonth && $isQuarterlyMonth) {
$this->processReports(AutomatedReportsService::FREQUENCY_QUARTERLY);
}
$this->logger->info(self::LOG_PREFIX . ' Completed');
return 0;
}
private function disableExpiredAskJiminnyReports(): void
{
$expiredReports = $this->reportRepository->getExpiredActiveAskJiminnyReports();
foreach ($expiredReports as $report) {
$this->logger->info(self::LOG_PREFIX . ' Disabling expired Ask Jiminny report', [
'reportUuid' => $report->getUuid(),
'teamId' => $report->getTeamId(),
'expiresAt' => $report->getExpiresAt()?->toDateString(),
]);
$this->reportRepository->update($report, ['status' => false]);
}
}
/**
* Process reports for a specific frequency.
*
* @param string $frequency
*
* @return void
*/
private function processReports(string $frequency): void
{
$this->logger->info(self::LOG_PREFIX . " Processing $frequency reports");
$reportId = $this->option('report-id');
if ($reportId !== null) {
$reports = $this->getReportById($reportId);
} else {
// Get all enabled, not deleted reports with active teams for the specified frequency
$reports = $this->reportRepository->getActiveReportsByFrequency($frequency);
}
$this->logger->info(self::LOG_PREFIX . " Found {$reports->count()} $frequency reports to process");
/** @var AutomatedReport $report */
foreach ($reports as $report) {
$this->logger->info(self::LOG_PREFIX . ' Dispatching Generate Report job for report', [
'reportUuid' => $report->getUuid(),
'teamId' => $report->getTeamId(),
'frequency' => $report->getFrequency(),
'type' => $report->getType(),
]);
$job = $report->isAskJiminnyReport()
? new RequestGenerateAskJiminnyReportJob($report->getUuid())
: new RequestGenerateReportJob($report->getUuid());
$this->dispatcher->dispatch($job);
}
}
private function getReportById(string $reportId): Collection
{
$report = $this->reportRepository->findByIdOrUuid($reportId);
if ($report === null) {
$this->logger->warning(self::LOG_PREFIX . ' Report not found for --report-id', ['reportId' => $reportId]);
$this->warn("Report not found: {$reportId}");
return collect();
}
if (! $report->getStatus()) {
$this->logger->warning(self::LOG_PREFIX . ' Report is inactive, processing anyway (manual override)', [
'reportId' => $reportId,
'reportUuid' => $report->getUuid(),
]);
$this->warn('Report is inactive — processing anyway (manual override).');
}
$team = $report->getTeam();
if ($team->getStatus() !== Team::STATUS_ACTIVE) {
$this->logger->warning(self::LOG_PREFIX . ' Team is not active, processing anyway (manual override)', [
'reportId' => $reportId,
'reportUuid' => $report->getUuid(),
'teamId' => $report->getTeamId(),
'teamStatus' => $team->getStatus(),
]);
$this->warn("Team #{$report->getTeamId()} is not active — processing anyway (manual override).");
}
if ($report->isExpired()) {
$this->logger->warning(self::LOG_PREFIX . ' Report is expired, processing anyway (manual override)', [
'reportId' => $reportId,
'reportUuid' => $report->getUuid(),
'expiresAt' => $report->getExpiresAt()?->toDateString(),
]);
$this->warn('Report is expired (expires_at: ' . $report->getExpiresAt()?->toDateString()
. ') — processing anyway (manual override).');
}
$this->info(self::LOG_PREFIX . ' Automated report found ' . $report->getCustomName());
return collect([$report]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
[2026-04-21 15:54:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"43a6a295-db0b-41c8-a1e7-ea653fc4c962","trace_id":"7e1614de-256e-4b7c-a415-e14bcc5028fb"}
[2026-04-21 15:54:11] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"43a6a295-db0b-41c8-a1e7-ea653fc4c962","trace_id":"7e1614de-256e-4b7c-a415-e14bcc5028fb"}
[2026-04-21 15:54:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"43a6a295-db0b-41c8-a1e7-ea653fc4c962","trace_id":"7e1614de-256e-4b7c-a415-e14bcc5028fb"}
[2026-04-21 15:54:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"764031a2-4849-4aaf-8577-4a978e6954e1","trace_id":"8f1576e8-e3c5-487c-95a3-62abbea2180d"}
[2026-04-21 15:54:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"764031a2-4849-4aaf-8577-4a978e6954e1","trace_id":"8f1576e8-e3c5-487c-95a3-62abbea2180d"}
[2026-04-21 15:54:14] local.NOTICE: Monitoring start {"correlation_id":"9349458f-2b92-40de-a1c3-dafb56c0384a","trace_id":"d6a53843-b794-4be5-ab1a-efdf9f071e86"}
[2026-04-21 15:54:14] local.NOTICE: Monitoring end {"correlation_id":"9349458f-2b92-40de-a1c3-dafb56c0384a","trace_id":"d6a53843-b794-4be5-ab1a-efdf9f071e86"}
[2026-04-21 15:54:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"655e9b4f-f82c-43ca-a233-e5e83782115a","trace_id":"684a64f7-64f7-4219-ac05-8ae999581e52"}
[2026-04-21 15:54:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"655e9b4f-f82c-43ca-a233-e5e83782115a","trace_id":"684a64f7-64f7-4219-ac05-8ae999581e52"}
[2026-04-21 15:54:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:18] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:18] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:21] local.INFO: Running conference:monitor:count command for activities in (2026-04-21 15:52:00, 2026-04-21 15:54:00] {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:21] local.INFO: [conference:monitor:count] No activities found in (2026-04-21 15:52:00, 2026-04-21 15:54:00] {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:22] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:22] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:22] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:23] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:24] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"2a02c773-bf80-4fb0-9e10-87f0c76b46ee","trace_id":"6acd6e8d-0ae7-4af6-9c36-c2c670ad1f16"}
[2026-04-21 15:55:02] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d4a99bac-1106-4db2-9ad4-f190b4799c79","trace_id":"795d34a8-e7b3-4c1e-963f-0977b6064ddb"}
[2026-04-21 15:55:02] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"d4a99bac-1106-4db2-9ad4-f190b4799c79","trace_id":"795d34a8-e7b3-4c1e-963f-0977b6064ddb"}
[2026-04-21 15:55:02] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d4a99bac-1106-4db2-9ad4-f190b4799c79","trace_id":"795d34a8-e7b3-4c1e-963f-0977b6064ddb"}
[2026-04-21 15:55:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"eafdbe55-6ead-4613-bfb2-9a00014700a3","trace_id":"564569e0-8952-4808-b15d-7f94e3f23905"}
[2026-04-21 15:55:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"eafdbe55-6ead-4613-bfb2-9a00014700a3","trace_id":"564569e0-8952-4808-b15d-7f94e3f23905"}
[2026-04-21 15:55:04] local.NOTICE: Monitoring start {"correlation_id":"fd2b4fe8-e2ee-4cd4-8b59-105ca2f87c90","trace_id":"dbe82434-c308-40bc-a5a9-b85f2119f8dc"}
[2026-04-21 15:55:04] local.NOTICE: Monitoring end {"correlation_id":"fd2b4fe8-e2ee-4cd4-8b59-105ca2f87c90","trace_id":"dbe82434-c308-40bc-a5a9-b85f2119f8dc"}
[2026-04-21 15:55:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"76d8d837-75a2-4740-bb02-3877fa3e1619","trace_id":"179b7945-aa2a-4b6a-90c1-98d25d097ec6"}
[2026-04-21 15:55:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"76d8d837-75a2-4740-bb02-3877fa3e1619","trace_id":"179b7945-aa2a-4b6a-90c1-98d25d097ec6"}
[2026-04-21 15:55:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:06] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:06] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bdf4f293-13e7-413c-b4d5-c8509d7a682d","trace_id":"cab56aca-defd-4b31-829c-9d17293e7f77"}
[2026-04-21 15:55:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bdf4f293-13e7-413c-b4d5-c8509d7a682d","trace_id":"cab56aca-defd-4b31-829c-9d17293e7f77"}
[2026-04-21 15:55:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"61f74536-9b81-4bf0-9c95-41e12eb8e16f","trace_id":"a58b79eb-1b2a-4518-8090-04680b782ead"}
[2026-04-21 15:55:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"61f74536-9b81-4bf0-9c95-41e12eb8e16f","trace_id":"a58b79eb-1b2a-4518-8090-04680b782ead"}
[2026-04-21 15:55:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"1daa2593-1a94-4ad9-95e4-a1e337af24ce","trace_id":"901278a1-bff0-49de-a2b4-daeab62f4597"}
[2026-04-21 15:55:10] local.INFO: Running pre-meeting notification command {"correlation_id":"1daa2593-1a94-4ad9-95e4-a1e337af24ce","trace_id":"901278a1-bff0-49de-a2b4-daeab62f4597"}
[2026-04-21 15:55:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"1daa2593-1a94-4ad9-95e4-a1e337af24ce","trace_id":"901278a1-bff0-49de-a2b4-daeab62f4597"}
[2026-04-21 15:55:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:11] local.INFO: Running conference:monitor:start command for activities in (2026-04-21 15:45:00, 2026-04-21 15:50:00] {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:11] local.INFO: [conference:monitor:start] No activities found in (2026-04-21 15:45:00, 2026-04-21 15:50:00] {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:14] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"15:50","to":"15:55"} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:14] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"05:45","to":"05:50"} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:15] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:15] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:15] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:15] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a8f4bf5b-80fd-4b84-94da-a70e91b7aaf2","trace_id":"ad43718a-ee59-406f-9ae7-8960d0f87e1a"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"34928b62-8c92-441a-87d8-1e052098bf0e","trace_id":"51615f90-90d3-473b-8cc8-68bbc265370d"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"34928b62-8c92-441a-87d8-1e052098bf0e","trace_id":"51615f90-90d3-473b-8cc8-68bbc265370d"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":60.0} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-21T15:57:19.857859Z"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a8f4bf5b-80fd-4b84-94da-a70e91b7aaf2","trace_id":"ad43718a-ee59-406f-9ae7-8960d0f87e1a"}
[2026-04-21 15:55:20] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:25] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:25] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:25] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:30] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:45] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:45] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:45] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9da28263-2731-436d-b4bb-9c2848f55d5b","trace_id":"c62980fc-13a2-4ea6-a1f7-2413cb9a74c1"}
[2026-04-21 15:56:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"9da28263-2731-436d-b4bb-9c2848f55d5b","trace_id":"c62980fc-13a2-4ea6-a1f7-2413cb9a74c1"}
[2026-04-21 15:56:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9da28263-2731-436d-b4bb-9c2848f55d5b","trace_id":"c62980fc-13a2-4ea6-a1f7-2413cb9a74c1"}
[2026-04-21 15:56:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9831fa44-e519-40e5-a3c6-cd733e941f76","trace_id":"4cca8604-873a-417d-b3c9-b769834a45c7"}
[2026-04-21 15:56:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9831fa44-e519-40e5-a3c6-cd733e941f76","trace_id":"4cca8604-873a-417d-b3c9-b769834a45c7"}
[2026-04-21 15:56:08] local.NOTICE: Monitoring start {"correlation_id":"5dfa33d3-b56d-45e3-9973-41f8a27c1227","trace_id":"8f01592a-0cab-460d-8516-a7115a3aceb7"}
[2026-04-21 15:56:08] local.NOTICE: Monitoring end {"correlation_id":"5dfa33d3-b56d-45e3-9973-41f8a27c1227","trace_id":"8f01592a-0cab-460d-8516-a7115a3aceb7"}
[2026-04-21 15:56:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8038ab28-a435-4b52-8b9d-ce064213ac31","trace_id":"57d20a2f-4bab-4da1-8a88-5f5bc76d441b"}
[2026-04-21 15:56:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8038ab28-a435-4b52-8b9d-ce064213ac31","trace_id":"57d20a2f-4bab-4da1-8a88-5f5bc76d441b"}
[2026-04-21 15:56:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:13] local.INFO: Running conference:monitor:count command for activities in (2026-04-21 15:54:00, 2026-04-21 15:56:00] {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:13] local.INFO: [conference:monitor:count] No activities found in (2026-04-21 15:54:00, 2026-04-21 15:56:00] {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"621229bb-57a7-4932-a824-ee4f71f15e11","trace_id":"f2d50dc8-9d97-41ba-92ba-07a30001326b"}
[2026-04-21 15:56:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"621229bb-57a7-4932-a824-ee4f71f15e11","trace_id":"f2d50dc8-9d97-41ba-92ba-07a30001326b"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":57,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":236.5,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.72} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:17] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:17] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":1} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:18] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"b3492731-d118-49df-a5bd-8e0959f381bf","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:18] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"b3492731-d118-49df-a5bd-8e0959f381bf","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:18] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"b3492731-d118-49df-a5bd-8e0959f381bf","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ead8e3ed-79cc-4e37-9f4d-3c1585382070","trace_id":"c84b841b-03f9-436e-a3fa-e6cd3157a14c"}
[2026-04-21 15:57:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"ead8e3ed-79cc-4e37-9f4d-3c1585382070","trace_id":"c84b841b-03f9-436e-a3fa-e6cd3157a14c"}
[2026-04-21 15:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ead8e3ed-79cc-4e37-9f4d-3c1585382070","trace_id":"c84b841b-03f9-436e-a3fa-e6cd3157a14c"}
[2026-04-21 15:57:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4f620cbc-50dd-4c7c-80d4-04c243431d58","trace_id":"490d6423-679c-47bf-bfee-8b755e68cbf9"}
[2026-04-21 15:57:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4f620cbc-50dd-4c7c-80d4-04c243431d58","trace_id":"490d6423-679c-47bf-bfee-8b755e68cbf9"}
[2026-04-21 15:57:11] local.NOTICE: Monitoring start {"correlation_id":"0bddffb7-0b6b-491a-8c28-2adbde41397c","trace_id":"815546d9-12fb-4e60-997b-d436aa61306f"}
[2026-04-21 15:57:11] local.NOTICE: Monitoring end {"correlation_id":"0bddffb7-0b6b-491a-8c28-2adbde41397c","trace_id":"815546d9-12fb-4e60-997b-d436aa61306f"}
[2026-04-21 15:57:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c1a75995-9bbd-48e3-9e81-7fc9160079bf","trace_id":"d4690f9c-eaff-48a9-9ce1-1db4bd9f6c68"}
[2026-04-21 15:57:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c1a75995-9bbd-48e3-9e81-7fc9160079bf","trace_id":"d4690f9c-eaff-48a9-9ce1-1db4bd9f6c68"}
[2026-04-21 15:57:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:14] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:14] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:15] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:15] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:16] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 1 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"f64b99d6-1e14-4ad4-93be-511ad0763d19","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"fe48747a-4f48-4d93-bff4-da38b7884b06","trace_id":"a3f17cc2-0555-472c-9174-56d6a06ad21e"}
[2026-04-21 15:58:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"fe48747a-4f48-4d93-bff4-da38b7884b06","trace_id":"a3f17cc2-0555-472c-9174-56d6a06ad21e"}
[2026-04-21 15:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"fe48747a-4f48-4d93-bff4-da38b7884b06","trace_id":"a3f17cc2-0555-472c-9174-56d6a06ad21e"}
[2026-04-21 15:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a5f038de-950d-4456-a5d4-b76a5bfc1e6c","trace_id":"5d51b727-26ec-4f53-bc58-0716efd0eb17"}
[2026-04-21 15:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a5f038de-950d-4456-a5d4-b76a5bfc1e6c","trace_id":"5d51b727-26ec-4f53-bc58-0716efd0eb17"}
[2026-04-21 15:58:06] local.NOTICE: Monitoring start {"correlation_id":"8b9433e0-9646-4c79-adbb-0b7d3a7cc8a8","trace_id":"cb65b9e7-57b5-45ac-a867-5d9ed7b511c0"}
[2026-04-21 15:58:06] local.NOTICE: Monitoring end {"correlation_id":"8b9433e0-9646-4c79-adbb-0b7d3a7cc8a8","trace_id":"cb65b9e7-57b5-45ac-a867-5d9ed7b511c0"}
[2026-04-21 15:58:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ab198a55-0f58-4975-8791-84ec2186721a","trace_id":"1ccdaef3-c807-40ce-bd35-7d84adc39f59"}
[2026-04-21 15:58:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ab198a55-0f58-4975-8791-84ec2186721a","trace_id":"1ccdaef3-c807-40ce-bd35-7d84adc39f59"}
[2026-04-21 15:58:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:10] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:10] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"429d5e09-a17d-44fb-a026-b48acb079d28","trace_id":"4c65bd72-bd47-4958-8c4a-e01dcaa7d957"}
[2026-04-21 15:58:12] local.INFO: Running conference:monitor:count command for activities in (2026-04-21 15:56:00, 2026-04-21 15:58:00] {"correlation_id":"429d5e09-a...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70710
|
|
70711
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
2
2
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands\Reports;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Support\Collection;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\Team;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
class AutomatedReportsCommand extends Command
{
/**
* Log prefix for all log messages
*/
private const string LOG_PREFIX = '[automated-reports]';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'automated-reports
{--report-id= : Process a specific report by ID or UUID (bypasses frequency scheduling)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Process automated reports based on their frequency (weekly, monthly, quarterly).
Use --report-id to manually trigger a specific report by ID or UUID.';
public function __construct(
private readonly LoggerInterface $logger,
private readonly BusDispatcher $dispatcher,
private readonly AutomatedReportsRepository $reportRepository
) {
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
$this->logger->info(self::LOG_PREFIX . ' Started');
$this->disableExpiredAskJiminnyReports();
$now = Carbon::now();
$isMonday = $now->isMonday();
$isFirstDayOfMonth = $now->day === 1;
$currentMonth = $now->month;
// Check if the current month is a quarterly month (January, April, July, October)
$isQuarterlyMonth = in_array($currentMonth, [1, 4, 7, 10], true);
$this->logger->info(self::LOG_PREFIX . ' Checking conditions', [
'isMonday' => $isMonday,
'isFirstDayOfMonth' => $isFirstDayOfMonth,
'currentMonth' => $currentMonth,
'isQuarterlyMonth' => $isQuarterlyMonth,
]);
// Process daily reports
$this->processReports(AutomatedReportsService::FREQUENCY_DAILY);
// Process weekly reports on Mondays
if ($isMonday) {
$this->processReports(AutomatedReportsService::FREQUENCY_WEEKLY);
}
// Process monthly reports on the first day of the month
if ($isFirstDayOfMonth) {
$this->processReports(AutomatedReportsService::FREQUENCY_MONTHLY);
}
// Process quarterly reports on the first day of January, April, July, and October
if ($isFirstDayOfMonth && $isQuarterlyMonth) {
$this->processReports(AutomatedReportsService::FREQUENCY_QUARTERLY);
}
$this->logger->info(self::LOG_PREFIX . ' Completed');
return 0;
}
private function disableExpiredAskJiminnyReports(): void
{
$expiredReports = $this->reportRepository->getExpiredActiveAskJiminnyReports();
foreach ($expiredReports as $report) {
$this->logger->info(self::LOG_PREFIX . ' Disabling expired Ask Jiminny report', [
'reportUuid' => $report->getUuid(),
'teamId' => $report->getTeamId(),
'expiresAt' => $report->getExpiresAt()?->toDateString(),
]);
$this->reportRepository->update($report, ['status' => false]);
}
}
/**
* Process reports for a specific frequency.
*
* @param string $frequency
*
* @return void
*/
private function processReports(string $frequency): void
{
$this->logger->info(self::LOG_PREFIX . " Processing $frequency reports");
$reportId = $this->option('report-id');
if ($reportId !== null) {
$reports = $this->getReportById($reportId);
} else {
// Get all enabled, not deleted reports with active teams for the specified frequency
$reports = $this->reportRepository->getActiveReportsByFrequency($frequency);
}
$this->logger->info(self::LOG_PREFIX . " Found {$reports->count()} $frequency reports to process");
/** @var AutomatedReport $report */
foreach ($reports as $report) {
$this->logger->info(self::LOG_PREFIX . ' Dispatching Generate Report job for report', [
'reportUuid' => $report->getUuid(),
'teamId' => $report->getTeamId(),
'frequency' => $report->getFrequency(),
'type' => $report->getType(),
]);
$job = $report->isAskJiminnyReport()
? new RequestGenerateAskJiminnyReportJob($report->getUuid())
: new RequestGenerateReportJob($report->getUuid());
$this->dispatcher->dispatch($job);
}
}
private function getReportById(string $reportId): Collection
{
$report = $this->reportRepository->findByIdOrUuid($reportId);
if ($report === null) {
$this->logger->warning(self::LOG_PREFIX . ' Report not found for --report-id', ['reportId' => $reportId]);
$this->warn("Report not found: {$reportId}");
return collect();
}
if (! $report->getStatus()) {
$this->logger->warning(self::LOG_PREFIX . ' Report is inactive, processing anyway (manual override)', [
'reportId' => $reportId,
'reportUuid' => $report->getUuid(),
]);
$this->warn('Report is inactive — processing anyway (manual override).');
}
$team = $report->getTeam();
if ($team->getStatus() !== Team::STATUS_ACTIVE) {
$this->logger->warning(self::LOG_PREFIX . ' Team is not active, processing anyway (manual override)', [
'reportId' => $reportId,
'reportUuid' => $report->getUuid(),
'teamId' => $report->getTeamId(),
'teamStatus' => $team->getStatus(),
]);
$this->warn("Team #{$report->getTeamId()} is not active — processing anyway (manual override).");
}
if ($report->isExpired()) {
$this->logger->warning(self::LOG_PREFIX . ' Report is expired, processing anyway (manual override)', [
'reportId' => $reportId,
'reportUuid' => $report->getUuid(),
'expiresAt' => $report->getExpiresAt()?->toDateString(),
]);
$this->warn('Report is expired (expires_at: ' . $report->getExpiresAt()?->toDateString()
. ') — processing anyway (manual override).');
}
$this->info(self::LOG_PREFIX . ' Automated report found ' . $report->getCustomName());
return collect([$report]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
[2026-04-21 15:54:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"43a6a295-db0b-41c8-a1e7-ea653fc4c962","trace_id":"7e1614de-256e-4b7c-a415-e14bcc5028fb"}
[2026-04-21 15:54:11] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"43a6a295-db0b-41c8-a1e7-ea653fc4c962","trace_id":"7e1614de-256e-4b7c-a415-e14bcc5028fb"}
[2026-04-21 15:54:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"43a6a295-db0b-41c8-a1e7-ea653fc4c962","trace_id":"7e1614de-256e-4b7c-a415-e14bcc5028fb"}
[2026-04-21 15:54:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"764031a2-4849-4aaf-8577-4a978e6954e1","trace_id":"8f1576e8-e3c5-487c-95a3-62abbea2180d"}
[2026-04-21 15:54:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"764031a2-4849-4aaf-8577-4a978e6954e1","trace_id":"8f1576e8-e3c5-487c-95a3-62abbea2180d"}
[2026-04-21 15:54:14] local.NOTICE: Monitoring start {"correlation_id":"9349458f-2b92-40de-a1c3-dafb56c0384a","trace_id":"d6a53843-b794-4be5-ab1a-efdf9f071e86"}
[2026-04-21 15:54:14] local.NOTICE: Monitoring end {"correlation_id":"9349458f-2b92-40de-a1c3-dafb56c0384a","trace_id":"d6a53843-b794-4be5-ab1a-efdf9f071e86"}
[2026-04-21 15:54:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"655e9b4f-f82c-43ca-a233-e5e83782115a","trace_id":"684a64f7-64f7-4219-ac05-8ae999581e52"}
[2026-04-21 15:54:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"655e9b4f-f82c-43ca-a233-e5e83782115a","trace_id":"684a64f7-64f7-4219-ac05-8ae999581e52"}
[2026-04-21 15:54:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:18] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:18] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:18] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"b1efd034-c0b1-4bfb-b7d0-6df327a40712","trace_id":"fda9f78f-26cb-4d30-9001-67a38e026025"}
[2026-04-21 15:54:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:21] local.INFO: Running conference:monitor:count command for activities in (2026-04-21 15:52:00, 2026-04-21 15:54:00] {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:21] local.INFO: [conference:monitor:count] No activities found in (2026-04-21 15:52:00, 2026-04-21 15:54:00] {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2fe8887a-7664-444f-b24a-b0e754b5bd8b","trace_id":"5c55dcc1-d8c9-46a7-ab12-e7dfc4a6808e"}
[2026-04-21 15:54:22] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:22] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:22] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:22] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:23] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3bab3210-b03c-4081-be47-e12f9dce14a0","trace_id":"1e01d9ab-cd41-4d87-8ca7-f342f5b66a3b"}
[2026-04-21 15:54:24] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"2a02c773-bf80-4fb0-9e10-87f0c76b46ee","trace_id":"6acd6e8d-0ae7-4af6-9c36-c2c670ad1f16"}
[2026-04-21 15:55:02] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d4a99bac-1106-4db2-9ad4-f190b4799c79","trace_id":"795d34a8-e7b3-4c1e-963f-0977b6064ddb"}
[2026-04-21 15:55:02] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"d4a99bac-1106-4db2-9ad4-f190b4799c79","trace_id":"795d34a8-e7b3-4c1e-963f-0977b6064ddb"}
[2026-04-21 15:55:02] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d4a99bac-1106-4db2-9ad4-f190b4799c79","trace_id":"795d34a8-e7b3-4c1e-963f-0977b6064ddb"}
[2026-04-21 15:55:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"eafdbe55-6ead-4613-bfb2-9a00014700a3","trace_id":"564569e0-8952-4808-b15d-7f94e3f23905"}
[2026-04-21 15:55:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"eafdbe55-6ead-4613-bfb2-9a00014700a3","trace_id":"564569e0-8952-4808-b15d-7f94e3f23905"}
[2026-04-21 15:55:04] local.NOTICE: Monitoring start {"correlation_id":"fd2b4fe8-e2ee-4cd4-8b59-105ca2f87c90","trace_id":"dbe82434-c308-40bc-a5a9-b85f2119f8dc"}
[2026-04-21 15:55:04] local.NOTICE: Monitoring end {"correlation_id":"fd2b4fe8-e2ee-4cd4-8b59-105ca2f87c90","trace_id":"dbe82434-c308-40bc-a5a9-b85f2119f8dc"}
[2026-04-21 15:55:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"76d8d837-75a2-4740-bb02-3877fa3e1619","trace_id":"179b7945-aa2a-4b6a-90c1-98d25d097ec6"}
[2026-04-21 15:55:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"76d8d837-75a2-4740-bb02-3877fa3e1619","trace_id":"179b7945-aa2a-4b6a-90c1-98d25d097ec6"}
[2026-04-21 15:55:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:06] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:06] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e28d6747-ff55-4d6e-a902-4616d8f4299c","trace_id":"5777dc94-758c-4181-a48f-101e52c8127e"}
[2026-04-21 15:55:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bdf4f293-13e7-413c-b4d5-c8509d7a682d","trace_id":"cab56aca-defd-4b31-829c-9d17293e7f77"}
[2026-04-21 15:55:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bdf4f293-13e7-413c-b4d5-c8509d7a682d","trace_id":"cab56aca-defd-4b31-829c-9d17293e7f77"}
[2026-04-21 15:55:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"61f74536-9b81-4bf0-9c95-41e12eb8e16f","trace_id":"a58b79eb-1b2a-4518-8090-04680b782ead"}
[2026-04-21 15:55:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"61f74536-9b81-4bf0-9c95-41e12eb8e16f","trace_id":"a58b79eb-1b2a-4518-8090-04680b782ead"}
[2026-04-21 15:55:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"1daa2593-1a94-4ad9-95e4-a1e337af24ce","trace_id":"901278a1-bff0-49de-a2b4-daeab62f4597"}
[2026-04-21 15:55:10] local.INFO: Running pre-meeting notification command {"correlation_id":"1daa2593-1a94-4ad9-95e4-a1e337af24ce","trace_id":"901278a1-bff0-49de-a2b4-daeab62f4597"}
[2026-04-21 15:55:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"1daa2593-1a94-4ad9-95e4-a1e337af24ce","trace_id":"901278a1-bff0-49de-a2b4-daeab62f4597"}
[2026-04-21 15:55:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:11] local.INFO: Running conference:monitor:start command for activities in (2026-04-21 15:45:00, 2026-04-21 15:50:00] {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:11] local.INFO: [conference:monitor:start] No activities found in (2026-04-21 15:45:00, 2026-04-21 15:50:00] {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"85c8794b-17fc-4d40-82a9-0c33abe958bb","trace_id":"8144639a-d0f1-4d0c-82cd-caeff6769e94"}
[2026-04-21 15:55:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:14] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"15:50","to":"15:55"} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:14] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"05:45","to":"05:50"} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ced4e1fa-9cb7-4944-91f2-f1c997989512","trace_id":"45e0449c-f21f-44cc-bc46-c623206162d2"}
[2026-04-21 15:55:15] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:15] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:15] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:15] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:16] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:17] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"aeb7e16d-acbb-4334-80d3-e2142ded8c84","trace_id":"343080b8-e562-4931-95f9-4b31fdb9bc62"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a8f4bf5b-80fd-4b84-94da-a70e91b7aaf2","trace_id":"ad43718a-ee59-406f-9ae7-8960d0f87e1a"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"34928b62-8c92-441a-87d8-1e052098bf0e","trace_id":"51615f90-90d3-473b-8cc8-68bbc265370d"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"34928b62-8c92-441a-87d8-1e052098bf0e","trace_id":"51615f90-90d3-473b-8cc8-68bbc265370d"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":60.0} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-21T15:57:19.857859Z"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a8f4bf5b-80fd-4b84-94da-a70e91b7aaf2","trace_id":"ad43718a-ee59-406f-9ae7-8960d0f87e1a"}
[2026-04-21 15:55:20] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:25] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:25] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:25] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:30] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:45] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:45] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:55:45] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9da28263-2731-436d-b4bb-9c2848f55d5b","trace_id":"c62980fc-13a2-4ea6-a1f7-2413cb9a74c1"}
[2026-04-21 15:56:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"9da28263-2731-436d-b4bb-9c2848f55d5b","trace_id":"c62980fc-13a2-4ea6-a1f7-2413cb9a74c1"}
[2026-04-21 15:56:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9da28263-2731-436d-b4bb-9c2848f55d5b","trace_id":"c62980fc-13a2-4ea6-a1f7-2413cb9a74c1"}
[2026-04-21 15:56:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9831fa44-e519-40e5-a3c6-cd733e941f76","trace_id":"4cca8604-873a-417d-b3c9-b769834a45c7"}
[2026-04-21 15:56:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9831fa44-e519-40e5-a3c6-cd733e941f76","trace_id":"4cca8604-873a-417d-b3c9-b769834a45c7"}
[2026-04-21 15:56:08] local.NOTICE: Monitoring start {"correlation_id":"5dfa33d3-b56d-45e3-9973-41f8a27c1227","trace_id":"8f01592a-0cab-460d-8516-a7115a3aceb7"}
[2026-04-21 15:56:08] local.NOTICE: Monitoring end {"correlation_id":"5dfa33d3-b56d-45e3-9973-41f8a27c1227","trace_id":"8f01592a-0cab-460d-8516-a7115a3aceb7"}
[2026-04-21 15:56:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8038ab28-a435-4b52-8b9d-ce064213ac31","trace_id":"57d20a2f-4bab-4da1-8a88-5f5bc76d441b"}
[2026-04-21 15:56:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8038ab28-a435-4b52-8b9d-ce064213ac31","trace_id":"57d20a2f-4bab-4da1-8a88-5f5bc76d441b"}
[2026-04-21 15:56:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:12] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:12] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"13144a5d-c9c7-4168-93f7-a09835a52b01","trace_id":"fdc0824d-9847-414e-a335-f7d04c8ebad2"}
[2026-04-21 15:56:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:13] local.INFO: Running conference:monitor:count command for activities in (2026-04-21 15:54:00, 2026-04-21 15:56:00] {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:13] local.INFO: [conference:monitor:count] No activities found in (2026-04-21 15:54:00, 2026-04-21 15:56:00] {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3d3b4fec-00df-4027-9150-8e82f3e97d0c","trace_id":"47588d56-a668-413d-924a-06adb891a27b"}
[2026-04-21 15:56:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"621229bb-57a7-4932-a824-ee4f71f15e11","trace_id":"f2d50dc8-9d97-41ba-92ba-07a30001326b"}
[2026-04-21 15:56:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"621229bb-57a7-4932-a824-ee4f71f15e11","trace_id":"f2d50dc8-9d97-41ba-92ba-07a30001326b"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":57,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":236.5,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.72} {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:16] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"31ab6a68-ec8c-4c4d-94b0-53c032a8d309","trace_id":"8eff7260-69ee-4764-84a3-9d18938195b7"}
[2026-04-21 15:56:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:17] local.INFO: [EmailSchedule] STARTING Inbox Sync {"host":"docker_lamp_1"} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:17] local.INFO: [EmailSchedule] FINISHED Inbox Sync {"host":"docker_lamp_1","events":1} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:sync","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e2d855bf-e534-42f3-b365-cbc59f14de64","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:18] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"b3492731-d118-49df-a5bd-8e0959f381bf","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:18] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"b3492731-d118-49df-a5bd-8e0959f381bf","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:56:18] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"b3492731-d118-49df-a5bd-8e0959f381bf","trace_id":"cedab4a6-c4a0-40c2-a94e-7b5813e8af3b"}
[2026-04-21 15:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ead8e3ed-79cc-4e37-9f4d-3c1585382070","trace_id":"c84b841b-03f9-436e-a3fa-e6cd3157a14c"}
[2026-04-21 15:57:07] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"ead8e3ed-79cc-4e37-9f4d-3c1585382070","trace_id":"c84b841b-03f9-436e-a3fa-e6cd3157a14c"}
[2026-04-21 15:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ead8e3ed-79cc-4e37-9f4d-3c1585382070","trace_id":"c84b841b-03f9-436e-a3fa-e6cd3157a14c"}
[2026-04-21 15:57:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4f620cbc-50dd-4c7c-80d4-04c243431d58","trace_id":"490d6423-679c-47bf-bfee-8b755e68cbf9"}
[2026-04-21 15:57:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4f620cbc-50dd-4c7c-80d4-04c243431d58","trace_id":"490d6423-679c-47bf-bfee-8b755e68cbf9"}
[2026-04-21 15:57:11] local.NOTICE: Monitoring start {"correlation_id":"0bddffb7-0b6b-491a-8c28-2adbde41397c","trace_id":"815546d9-12fb-4e60-997b-d436aa61306f"}
[2026-04-21 15:57:11] local.NOTICE: Monitoring end {"correlation_id":"0bddffb7-0b6b-491a-8c28-2adbde41397c","trace_id":"815546d9-12fb-4e60-997b-d436aa61306f"}
[2026-04-21 15:57:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c1a75995-9bbd-48e3-9e81-7fc9160079bf","trace_id":"d4690f9c-eaff-48a9-9ce1-1db4bd9f6c68"}
[2026-04-21 15:57:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c1a75995-9bbd-48e3-9e81-7fc9160079bf","trace_id":"d4690f9c-eaff-48a9-9ce1-1db4bd9f6c68"}
[2026-04-21 15:57:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:14] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:14] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8b4bfb4b-0732-43a8-a717-50864389cee8","trace_id":"cdc865f2-2519-4331-b259-3a2174a41950"}
[2026-04-21 15:57:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:15] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:15] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c64d9591-a922-4291-9d25-7e76834d412c","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:57:16] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 1 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"f64b99d6-1e14-4ad4-93be-511ad0763d19","trace_id":"b193011b-7d82-4a59-9538-3440dbb57e99"}
[2026-04-21 15:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"fe48747a-4f48-4d93-bff4-da38b7884b06","trace_id":"a3f17cc2-0555-472c-9174-56d6a06ad21e"}
[2026-04-21 15:58:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"fe48747a-4f48-4d93-bff4-da38b7884b06","trace_id":"a3f17cc2-0555-472c-9174-56d6a06ad21e"}
[2026-04-21 15:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"fe48747a-4f48-4d93-bff4-da38b7884b06","trace_id":"a3f17cc2-0555-472c-9174-56d6a06ad21e"}
[2026-04-21 15:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a5f038de-950d-4456-a5d4-b76a5bfc1e6c","trace_id":"5d51b727-26ec-4f53-bc58-0716efd0eb17"}
[2026-04-21 15:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a5f038de-950d-4456-a5d4-b76a5bfc1e6c","trace_id":"5d51b727-26ec-4f53-bc58-0716efd0eb17"}
[2026-04-21 15:58:06] local.NOTICE: Monitoring start {"correlation_id":"8b9433e0-9646-4c79-adbb-0b7d3a7cc8a8","trace_id":"cb65b9e7-57b5-45ac-a867-5d9ed7b511c0"}
[2026-04-21 15:58:06] local.NOTICE: Monitoring end {"correlation_id":"8b9433e0-9646-4c79-adbb-0b7d3a7cc8a8","trace_id":"cb65b9e7-57b5-45ac-a867-5d9ed7b511c0"}
[2026-04-21 15:58:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ab198a55-0f58-4975-8791-84ec2186721a","trace_id":"1ccdaef3-c807-40ce-bd35-7d84adc39f59"}
[2026-04-21 15:58:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ab198a55-0f58-4975-8791-84ec2186721a","trace_id":"1ccdaef3-c807-40ce-bd35-7d84adc39f59"}
[2026-04-21 15:58:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:10] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:10] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":60.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"45b2fe70-3237-471f-a225-6273f7051f84","trace_id":"9b9d2c5a-e0f0-4273-83c7-45e7632e05f4"}
[2026-04-21 15:58:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"429d5e09-a17d-44fb-a026-b48acb079d28","trace_id":"4c65bd72-bd47-4958-8c4a-e01dcaa7d957"}
[2026-04-21 15:58:12] local.INFO: Running conference:monitor:count command for activities in (2026-04-21 15:56:00, 2026-04-21 15:58:00] {"correlation_id":"429d5e09-a...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70711
|
|
70784
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
109
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReportResult::find(285);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
Analyzing…
[2026-04-22 11:17:46] local.INFO: [SyncHubspotObjects] Starting sync {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","usage":23220352,"real_usage":65011712,"pid":33084} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:46] local.INFO: [Sync Mailbox] Sync start {"inbox_id":59} {"correlation_id":"e244bc7d-da2a-481b-a526-4835f5675cb5","trace_id":"cf45a5b0-f7c8-4f7f-a8b3-ac60fd01ac52"}
[2026-04-22 11:17:46] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:46] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:46] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:46] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"hubspot","crm_owner":148,"team_id":2} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:46] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:46] local.INFO: [Inbox service] Skipping METADATA SYNC for inbox 59 due to unauthorized access to the mailbox {"correlation_id":"e244bc7d-da2a-481b-a526-4835f5675cb5","trace_id":"cf45a5b0-f7c8-4f7f-a8b3-ac60fd01ac52"}
[2026-04-22 11:17:46] local.INFO: [Sync Mailbox] Sync complete {"inbox_id":59} {"correlation_id":"e244bc7d-da2a-481b-a526-4835f5675cb5","trace_id":"cf45a5b0-f7c8-4f7f-a8b3-ac60fd01ac52"}
[2026-04-22 11:17:47] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"https://api.hubapi.com/crm/v3/objects/deals/search","total_requests":1,"total_records_fetched":0,"total_elapsed_seconds":0.81,"average_seconds_per_request":0.81} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [HubSpot] Synced opportunities {"team":2,"strategies":"lastModified","sync_count":0,"total":0,"last_synced_id":null,"duration_ms":866.47} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Sync finished {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","provider":"hubspot","status":"completed","duration_ms":1075.93,"usage":23675264,"real_usage":65011712,"pid":33084} {"correlation_id":"a5ea10e3-3a25-4581-9cac-d59aa7d2000c","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Starting sync {"team":"b2b115eb-93ce-4d1b-929c-173757df8fba","usage":23719392,"real_usage":65011712,"pid":33084} {"correlation_id":"2c1c09a6-2454-4dcc-bd80-ed690bdc7d14","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.WARNING: [HubSpot] Account not connected for user {"userId":"33e34a7a-1c02-4f04-87ac-22c3a385e6e3","account":{"Jiminny\\Models\\SocialAccount":{"id":306,"sociable_id":109,"provider_user_id":"11348452","expires":1701077403,"refresh_token_expires":null,"provider":"hubspot","state":"full-refresh","auth_scope":null,"retry_after":null,"created_at":"2020-09-01 16:59:04","updated_at":"2023-11-27 09:30:03"}}} {"correlation_id":"2c1c09a6-2454-4dcc-bd80-ed690bdc7d14","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"hubspot","crm_owner":109,"team_id":29} {"correlation_id":"2c1c09a6-2454-4dcc-bd80-ed690bdc7d14","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {"crm_provider":"hubspot","team_id":29} {"correlation_id":"2c1c09a6-2454-4dcc-bd80-ed690bdc7d14","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {"crm_provider":"hubspot","team_id":29} {"correlation_id":"2c1c09a6-2454-4dcc-bd80-ed690bdc7d14","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Sync finished {"team":"b2b115eb-93ce-4d1b-929c-173757df8fba","provider":"hubspot","status":"disconnected","duration_ms":80.99,"usage":24001560,"real_usage":65011712,"pid":33084,"reason":"Your HubSpot account has become disconnected. Please login to Jiminny to reconnect."} {"correlation_id":"2c1c09a6-2454-4dcc-bd80-ed690bdc7d14","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Starting sync {"team":"c6b9d6b0-b48d-4832-a68c-a57d60651888","usage":24039928,"real_usage":65011712,"pid":33084} {"correlation_id":"cacc84bb-9b2c-4900-a86f-09b35fa11e7a","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.WARNING: [HubSpot] Account not connected for user {"userId":"71e3aac5-fb66-47c5-a236-2d051ae3e319","account":null} {"correlation_id":"cacc84bb-9b2c-4900-a86f-09b35fa11e7a","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"hubspot","crm_owner":256,"team_id":49} {"correlation_id":"cacc84bb-9b2c-4900-a86f-09b35fa11e7a","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {"crm_provider":"hubspot","team_id":49} {"correlation_id":"cacc84bb-9b2c-4900-a86f-09b35fa11e7a","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {"crm_provider":"hubspot","team_id":49} {"correlation_id":"cacc84bb-9b2c-4900-a86f-09b35fa11e7a","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Sync finished {"team":"c6b9d6b0-b48d-4832-a68c-a57d60651888","provider":"hubspot","status":"disconnected","duration_ms":29.6,"usage":24281496,"real_usage":65011712,"pid":33084,"reason":"Social account for HubSpot cannot be found. Please login to Jiminny to connect."} {"correlation_id":"cacc84bb-9b2c-4900-a86f-09b35fa11e7a","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Starting sync {"team":"b2d49a54-b645-4637-a7ae-a86cfce6e8e4","usage":24319864,"real_usage":65011712,"pid":33084} {"correlation_id":"cf49eb13-7a25-4033-931d-141c271c8528","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.WARNING: [HubSpot] Account not connected for user {"userId":"2ac0447f-3c8c-4ce0-baeb-b63ddb76fa9b","account":null} {"correlation_id":"cf49eb13-7a25-4033-931d-141c271c8528","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"hubspot","crm_owner":130,"team_id":42} {"correlation_id":"cf49eb13-7a25-4033-931d-141c271c8528","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {"crm_provider":"hubspot","team_id":42} {"correlation_id":"cf49eb13-7a25-4033-931d-141c271c8528","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {"crm_provider":"hubspot","team_id":42} {"correlation_id":"cf49eb13-7a25-4033-931d-141c271c8528","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:47] local.INFO: [SyncHubspotObjects] Sync finished {"team":"b2d49a54-b645-4637-a7ae-a86cfce6e8e4","provider":"hubspot","status":"disconnected","duration_ms":29.88,"usage":24566456,"real_usage":65011712,"pid":33084,"reason":"Social account for HubSpot cannot be found. Please login to Jiminny to connect."} {"correlation_id":"cf49eb13-7a25-4033-931d-141c271c8528","trace_id":"874d2986-c64f-49e4-bc15-0fd7fba9f461"}
[2026-04-22 11:17:58] local.INFO: [automated-reports] Started {"correlation_id":"9f3241ff-e8da-4d44-86b6-856be6cd1a8f","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:17:58] local.INFO: [automated-reports] Checking conditions {"isMonday":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"9f3241ff-e8da-4d44-86b6-856be6cd1a8f","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:17:58] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"9f3241ff-e8da-4d44-86b6-856be6cd1a8f","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:17:58] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"9f3241ff-e8da-4d44-86b6-856be6cd1a8f","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:17:58] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"9f3241ff-e8da-4d44-86b6-856be6cd1a8f","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:17:58] local.INFO: [automated-reports] Completed {"correlation_id":"9f3241ff-e8da-4d44-86b6-856be6cd1a8f","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:17:58] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"cb7e4ce9-6b26-4a0f-8754-e7ec0ba9c8bf","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:18:00] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {"saved_search_id":1977,"user_id":143,"activity_count":0} {"correlation_id":"cb7e4ce9-6b26-4a0f-8754-e7ec0ba9c8bf","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:18:00] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"cb7e4ce9-6b26-4a0f-8754-e7ec0ba9c8bf","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:18:00] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"cb7e4ce9-6b26-4a0f-8754-e7ec0ba9c8bf","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:18:00] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","recipientsCount":1} {"correlation_id":"cb7e4ce9-6b26-4a0f-8754-e7ec0ba9c8bf","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:18:00] local.INFO: [Send Report Not Generated Mail] Email sent {"uuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf","email":"[EMAIL]","recipientName":"Lukas Kovalik"} {"correlation_id":"3ba418df-19df-480f-a8ad-0564fc39f63a","trace_id":"cb8c5518-7028-4860-b451-0c0dd31f925c"}
[2026-04-22 11:18:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a7453c0f-a3ed-460f-9b5f-a03bb55b3302","trace_id":"f539882b-846c-45d0-856f-49994b6eabf0"}
[2026-04-22 11:18:14] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"a7453c0f-a3ed-460f-9b5f-a03bb55b3302","trace_id":"f539882b-846c-45d0-856f-49994b6eabf0"}
[2026-04-22 11:18:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a7453c0f-a3ed-460f-9b5f-a03bb55b3302","trace_id":"f539882b-846c-45d0-856f-49994b6eabf0"}
[2026-04-22 11:18:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"caefedbc-714a-48b3-846b-bc68a389bfc6","trace_id":"57b2649a-aeb0-4260-bade-5ff3658007fa"}
[2026-04-22 11:18:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"caefedbc-714a-48b3-846b-bc68a389bfc6","trace_id":"57b2649a-aeb0-4260-bade-5ff3658007fa"}
[2026-04-22 11:18:31] local.NOTICE: Monitoring start {"correlation_id":"ec4e3d39-2101-4069-9a75-d86ba431823e","trace_id":"3bd99e3d-8190-49aa-81f1-d19210ae716f"}
[2026-04-22 11:18:31] local.NOTICE: Monitoring end {"correlation_id":"ec4e3d39-2101-4069-9a75-d86ba431823e","trace_id":"3bd99e3d-8190-49aa-81f1-d19210ae716f"}
[2026-04-22 11:18:39] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"af40018a-fe95-4199-b081-9dd74700fde2","trace_id":"9b0ecd36-499f-4e2f-886e-e8ba328c5491"}
[2026-04-22 11:18:39] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"af40018a-fe95-4199-b081-9dd74700fde2","trace_id":"9b0ecd36-499f-4e2f-886e-e8ba328c5491"}
[2026-04-22 11:18:47] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"15a614e7-b912-498d-b0ff-5b838172a4fd","trace_id":"11e2c22a-ec77-4003-985e-188258c51e42"}
[2026-04-22 11:18:47] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"15a614e7-b912-498d-b0ff-5b838172a4fd","trace_id":"11e2c22a-ec77-4003-985e-188258c51e42"}
[2026-04-22 11:18:47] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"15a614e7-b912-498d-b0ff-5b838172a4fd","trace_id":"11e2c22a-ec77-4003-985e-188258c51e42"}
[2026-04-22 11:18:47] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"15a614e7-b912-498d-b0ff-5b838172a4fd","trace_id":"11e2c22a-ec77-4003-985e-188258c51e42"}
[2026-04-22 11:18:54] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ef5d0b3e-d872-40e5-b80c-a85e78e921a8","trace_id":"bb760af6-817e-4e56-8b72-6db3c3d58675"}
[2026-04-22 11:18:54] local.INFO: Running conference:monitor:count command for activities in (2026-04-22 11:16:00, 2026-04-22 11:18:00] {"correlation_id":"ef5d0b3e-d872-40e5-b80c-a85e78e921a8","trace_id":"bb760af6-817e-4e56-8b72-6db3c3d58675"}
[2026-04-22 11:18:54] local.INFO: [conference:monitor:count] No activities found in (2026-04-22 11:16:00, 2026-04-22 11:18:00] {"correlation_id":"ef5d0b3e-d872-40e5-b80c-a85e78e921a8","trace_id":"bb760af6-817e-4e56-8b72-6db3c3d58675"}
[2026-04-22 11:18:54] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ef5d0b3e-d872-40e5-b80c-a85e78e921a8","trace_id":"bb760af6-817e-4e56-8b72-6db3c3d58675"}
[2026-04-22 11:19:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"949c42b5-cff7-483e-a17a-e2c1893958c5","trace_id":"ad50f410-8295-4e4a-a247-8bed468e8819"}
[2026-04-22 11:19:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"949c42b5-cff7-483e-a17a-e2c1893958c5","trace_id":"ad50f410-8295-4e4a-a247-8bed468e8819"}
[2026-04-22 11:20:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"91ff74fa-8c63-4d11-949d-b72a2d3ec9ee","trace_id":"eaf4f12f-317e-4c0a-ae3f-dd9a1aa8f70f"}
[2026-04-22 11:20:17] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"91ff74fa-8c63-4d11-949d-b72a2d3ec9ee","trace_id":"eaf4f12f-317e-4c0a-ae3f-dd9a1aa8f70f"}
[2026-04-22 11:20:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"91ff74fa-8c63-4d11-949d-b72a2d3ec9ee","trace_id":"eaf4f12f-317e-4c0a-ae3f-dd9a1aa8f70f"}
[2026-04-22 11:20:24] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d27e07d3-0722-42e3-a120-e44c2f6bd5f7","trace_id":"b6c3fa10-1211-4225-a253-7371509b203a"}
[2026-04-22 11:20:24] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d27e07d3-0722-42e3-a120-e44c2f6bd5f7","trace_id":"b6c3fa10-1211-4225-a253-7371509b203a"}
[2026-04-22 11:20:30] local.NOTICE: Monitoring start {"correlation_id":"581583a3-3491-4420-8996-b16978039344","trace_id":"2d32c838-2600-4ad9-a883-8576a71ea284"}
[2026-04-22 11:20:30] local.NOTICE: Monitoring end {"correlation_id":"581583a3-3491-4420-8996-b16978039344","trace_id":"2d32c838-2600-4ad9-a883-8576a71ea284"}
[2026-04-22 11:20:37] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"7f7f379e-612a-46af-9355-20879ca9b30f","trace_id":"b199e1e2-2814-4fff-9a2e-9eba675d5bfc"}
[2026-04-22 11:20:37] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"7f7f379e-612a-46af-9355-20879ca9b30f","trace_id":"b199e1e2-2814-4fff-9a2e-9eba675d5bfc"}
[2026-04-22 11:20:43] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"de646be6-00bf-4689-9028-51242f04d5d2","trace_id":"bb1ad6d2-1e01-4c52-9536-8a65733c30bf"}
[2026-04-22 11:20:43] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"de646be6-00bf-4689-9028-51242f04d5d2","trace_id":"bb1ad6d2-1e01-4c52-9536-8a65733c30bf"}
[2026-04-22 11:20:43] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"de646be6-00bf-4689-9028-51242f04d5d2","trace_id":"bb1ad6d2-1e01-4c52-9536-8a65733c30bf"}
[2026-04-22 11:20:43] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"de646be6-00bf-4689-9028-51242f04d5d2","trace_id":"bb1ad6d2-1e01-4c52-9536-8a65733c30bf"}
[2026-04-22 11:20:49] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e182496c-06ef-4dd8-862d-5bd056493e64","trace_id":"29c69710-d465-43f7-b4d5-9a088ba55d0f"}
[2026-04-22 11:20:49] local.INFO: Running conference:monitor:count command for activities in (2026-04-22 11:18:00, 2026-04-22 11:20:00] {"correlation_id":"e182496c-06ef-4dd8-862d-5bd056493e64","trace_id":"29c69710-d465-43f7-b4d5-9a088ba55d0f"}
[2026-04-22 11:20:49] local.INFO: [conference:monitor:count] No activities found in (2026-04-22 11:18:00, 2026-04-22 11:20:00] {"correlation_id":"e182496c-06ef-4dd8-862d-5bd056493e64","trace_id":"29c69710-d465-43f7-b4d5-9a088ba55d0f"}
[2026-04-22 11:20:49] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e182496c-06ef-4dd8-862d-5bd056493e64","trace_id":"29c69710-d465-43f7-b4d5-9a088ba55d0f"}
[2026-04-22 11:20:55] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9463f36f-0a17-4164-8e3f-00a22f37c05c","trace_id":"db968be6-8c7e-4c21-aaec-6f3cfe364161"}
[2026-04-22 11:20:55] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9463f36f-0a17-4164-8e3f-00a22f37c05c","trace_id":"db968be6-8c7e-4c21-aaec-6f3cfe364161"}
[2026-04-22 11:21:00] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bf99408a-5476-46ac-9f85-acfd1bbfe9e7","trace_id":"d9c6d38e-cbba-4e91-b28d-42eef38e9735"}
[2026-04-22 11:21:01] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bf99408a-5476-46ac-9f85-acfd1bbfe9e7","trace_id":"d9c6d38e-cbba-4e91-b28d-42eef38e9735"}
[2026-04-22 11:21:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"57cb09fe-18a1-4b5a-98dc-b7ac1b3d3060","trace_id":"9fe26e72-8fc7-47a6-bafc-4813a855abe9"}
[2026-04-22 11:21:07] local.INFO: Running pre-meeting notification command {"correlation_id":"57cb09fe-18a1-4b5a-98dc-b7ac1b3d3060","trace_id":"9fe26e72-8fc7-47a6-bafc-4813a855abe9"}
[2026-04-22 11:21:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"57cb09fe-18a1-4b5a-98dc-b7ac1b3d3060","trace_id":"9fe26e72-8fc7-47a6-bafc-4813a855abe9"}
[2026-04-22 11:21:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"98f58e05-6e53-47c4-be93-c68389339e8f","trace_id":"01a1daeb-5a99-4db2-b9dc-775a9200b16e"}
[2026-04-22 11:21:12] local.INFO: Running conference:monitor:start command for activities in (2026-04-22 11:11:00, 2026-04-22 11:16:00] {"correlation_id":"98f58e05-6e53-47c4-be93-c68389339e8f","trace_id":"01a1daeb-5a99-4db2-b9dc-775a9200b16e"}
[2026-04-22 11:21:13] local.INFO: [conference:monitor:start] No activities found in (2026-04-22 11:11:00, 2026-04-22 11:16:00] {"correlation_id":"98f58e05-6e53-47c4-be93-c68389339e8f","trace_id":"01a1daeb-5a99-4db2-b9dc-775a9200b16e"}
[2026-04-22 11:21:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"98f58e05-6e53-47c4-be93-c68389339e8f","trace_id":"01a1daeb-5a99-4db2-b9dc-775a9200b16e"}
[2026-04-22 11:21:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"73739c71-e718-4a2b-ac69-cb3ddc7d92d9","trace_id":"49217a79-ee6a-44fa-906b-1893235d8570"}
[2026-04-22 11:21:19] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"11:16","to":"11:21"} {"correlation_id":"73739c71-e718-4a2b-ac69-cb3ddc7d92d9","trace_id":"49217a79-ee6a-44fa-906b-1893235d8570"}
[2026-04-22 11:21:19] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"01:11","to":"01:16"} {"correlation_id":"73739c71-e718-4a2b-ac69-cb3ddc7d92d9","trace_id":"49217a79-ee6a-44fa-906b-1893235d8570"}
[2026-04-22 11:21:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"73739c71-e718-4a2b-ac69-cb3ddc7d92d9","trace_id":"49217a79-ee6a-44fa-906b-1893235d8570"}
[2026-04-22 11:21:25] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:25] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:26] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:26] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"5d289318-f26a-42e3-9912-8d1f83823e50","trace_id":"8150b91d-ebe2-4396-a537-e6ccd16c6593"}
[2026-04-22 11:21:35] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"jiminny:transcription:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d0277b2a-f3eb-407c-9515-ac5852fc2e73","trace_id":"92482f36-4718-4cbd-a3d0-23578f1a01cc"}
[2026-04-22 11:21:35] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"5d6594be-8d69-40ac-babb-626758ef357e","trace_id":"49bae09f-e422-4f35-8471-048f30e6f8b7"}
[2026-04-22 11:21:35] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"jiminny:transcription:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d0277b2a-f3eb-407c-9515-ac5852fc2e73","trace_id":"92482f36-4718-4cbd-a3d0-23578f1a01cc"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-22T11:23:35.818565Z"} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:35] local.INFO: [HubSpot Journal Auth] Requesting new client credentials token {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:36] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"5d6594be-8d69-40ac-babb-626758ef357e","trace_id":"49bae09f-e422-4f35-8471-048f30e6f8b7"}
[2026-04-22 11:21:36] local.INFO: [HubSpot Journal Auth] Successfully obtained new access token {"expires_in":1800,"cached_for":1500} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:36] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:41] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:41] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:41] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:reset-governor","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8cdece4d-c78e-4138-9895-f4f27f5935be","trace_id":"5027075c-e906-4282-9bc6-13a1e46eca1c"}
[2026-04-22 11:21:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:reset-governor","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8cdece4d-c78e-4138-9895-f4f27f5935be","trace_id":"5027075c-e906-4282-9bc6-13a1e46eca1c"}
[2026-04-22 11:21:46] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:46] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:47] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:21:51] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"436e8072-4ff9-45e2-9915-976338d7abf6","trace_id":"e478dcdc-eaa1-4527-82fb-26804797adba"}
[2026-04-22 11:21:51] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"436e8072-4ff9-45e2-9915-976338d7abf6","trace_id":"e478dcdc-eaa1-4527-82fb-26804797adba"}
[2026-04-22 11:22:02] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:22:02] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:22:02] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:22:17] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":33693,"workerId":"","target":"activities"} {"correlation_id":"925901c6-c633-419c-8cd5-327df3c0de36","trace_id":"7b5a944c-f17e-4ada-8984-309ef94639d0"}
[2026-04-22 11:22:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"7e91a2e9-92dc-4158-9456-26557a0d0eb5","trace_id":"d4bc050f-49c7-4b38-9a2c-dc9cb6eed0cb"}
[2026-04-22 11:22:26] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"7e91a2e9-92dc-4158-9456-26557a0d0eb5","trace_id":"d4bc050f-49c7-4b38-9a2c-dc9cb6eed0cb"}
[2026-04-22 11:22:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"7e91a2e9-92dc-4158-9456-26557a0d0eb5","trace_id":"d4bc050f-49c7-4b38-9a2c-dc9cb6eed0cb"}
[2026-04-22 11:22:32] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:22:32] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765","trace_id":"c95f83e8-7371-431f-8a24-b4ffe65813fd"}
[2026-04-22 11:22:32] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a1ab76af-f7ed-456f-8a20-d022faaee490","trace_id":"cc95fe23-828d-4c15-bebd-e33e65617b85"}
[2026-04-22 11:22:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a1ab76af-f7ed-456f-8a20-d022faaee490","trace_id":"cc95fe23-828d-4c15-bebd-e33e65617b85"}
[2026-04-22 11:22:33] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"c16793e0-5830-4500-bee3-2b2959287765"...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70784
|
|
70845
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
1
110
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReportResult::find(285);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
exit(1);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
324
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:23:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"7768a9ba-2aff-4c7a-8762-77fddf39166c","trace_id":"70919d83-95b0-47ad-ba65-72f90d555a58"}
[2026-04-22 11:23:08] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"7768a9ba-2aff-4c7a-8762-77fddf39166c","trace_id":"70919d83-95b0-47ad-ba65-72f90d555a58"}
[2026-04-22 11:23:08] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"7768a9ba-2aff-4c7a-8762-77fddf39166c","trace_id":"70919d83-95b0-47ad-ba65-72f90d555a58"}
[2026-04-22 11:23:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"7768a9ba-2aff-4c7a-8762-77fddf39166c","trace_id":"70919d83-95b0-47ad-ba65-72f90d555a58"}
[2026-04-22 11:23:10] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"b8f85461-e211-4519-a1aa-2db9db63a86b","trace_id":"70919d83-95b0-47ad-ba65-72f90d555a58"}
[2026-04-22 11:23:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"b9bcf67f-e460-4bef-b1aa-f2a09412cf46","trace_id":"ff5362a8-86a6-47b6-8cf6-04225ef846ab"}
[2026-04-22 11:23:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"b9bcf67f-e460-4bef-b1aa-f2a09412cf46","trace_id":"ff5362a8-86a6-47b6-8cf6-04225ef846ab"}
[2026-04-22 11:23:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"twilio:recover-tracks","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"535a9ebc-8e55-4b77-bdf0-eb0ced31da04","trace_id":"8d796f81-86a9-415b-8597-a5d34555bbd3"}
[2026-04-22 11:23:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"twilio:recover-tracks","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"535a9ebc-8e55-4b77-bdf0-eb0ced31da04","trace_id":"8d796f81-86a9-415b-8597-a5d34555bbd3"}
[2026-04-22 11:23:14] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf"} {"correlation_id":"6b94d178-5394-4417-8996-9b8580bc1396","trace_id":"d4cfff0e-85bf-4b35-a8c0-d0752235ffc4"}
[2026-04-22 11:23:15] local.ERROR: [AskJiminnyReport:Generate] Error {"automatedReportUuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf","reportUuid":null,"code":0,"message":"Report not found"} {"correlation_id":"6b94d178-5394-4417-8996-9b8580bc1396","trace_id":"d4cfff0e-85bf-4b35-a8c0-d0752235ffc4"}
[2026-04-22 11:23:15] local.INFO: [AskJiminnyReport:Generate] Retry scheduled {"attempts":1} {"correlation_id":"6b94d178-5394-4417-8996-9b8580bc1396","trace_id":"d4cfff0e-85bf-4b35-a8c0-d0752235ffc4"}
[2026-04-22 11:23:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:sync-users","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: Skip provider synchronisation, no teams found {"provider":"connect-and-sell"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: Start user synchronisation {"provider":"justcall","teams_count":1} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: Synchronising team {"provider":"justcall","team_id":1} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.WARNING: [Salesforce] Account not connected for user {"userId":"cdf9285a-8ded-4a8b-bd7d-ec68c398f2f9","account":{"Jiminny\\Models\\SocialAccount":{"id":1367,"sociable_id":1071,"provider_user_id":"005O4000003s5c7IAA","expires":null,"refresh_token_expires":null,"provider":"salesforce","state":"full-refresh","auth_scope":"refresh_token web api","retry_after":null,"created_at":"2024-09-10 07:05:21","updated_at":"2026-01-14 07:00:58"}}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"salesforce","crm_owner":1071,"team_id":1} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [CrmOwnerResolver] TeamMember found with active crm connection {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1497,"provider":"justcall"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1497,"provider":"justcall"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:15] local.INFO: [JustCall] Fetching users {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":352648,"owner_id":352648,"firstname":"Nicholas","lastname":"Ruggieri","email":"[EMAIL]","on_call":0,"last_login":"2026-04-16 14:44:52","availability":0}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":355694,"owner_id":352648,"firstname":"Jeff","lastname":"Bickhaus","email":"[EMAIL]","on_call":0,"last_login":"2026-04-20 15:33:47","availability":0}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":373651,"owner_id":352648,"firstname":"JohnFranco","lastname":"Archuleta","email":"[EMAIL]","on_call":0,"last_login":"2026-04-15 21:14:20","availability":0}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":373652,"owner_id":352648,"firstname":"Sharminy","lastname":"Eaton","email":"[EMAIL]","on_call":0,"last_login":"2026-04-20 19:46:25","availability":1}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":390335,"owner_id":352648,"firstname":"Benjamin","lastname":"Grossman","email":"[EMAIL]","on_call":0,"last_login":"2025-05-28 14:48:33","availability":0}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":398216,"owner_id":352648,"firstname":"Dawn","lastname":"Harry","email":"[EMAIL]","on_call":0,"last_login":"2026-04-10 21:44:49","availability":1}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":419551,"owner_id":352648,"firstname":"Alfonso","lastname":"Azuaje","email":"[EMAIL]","on_call":0,"last_login":"2026-04-20 13:51:18","availability":0}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":449418,"owner_id":352648,"firstname":"Pinpoint","lastname":"Support Amy","email":"[EMAIL]","on_call":0,"last_login":"2026-03-23 17:42:44","availability":1}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Syncing external user {"userData":{"agent_id":449451,"owner_id":352648,"firstname":"Pinpoint","lastname":"Support Luis","email":"[EMAIL]","on_call":0,"last_login":"2026-04-21 13:39:13","availability":1}} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: User synchronisation complete {"provider":"justcall","team_id":1,"team":"jiminny"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Skip provider synchronisation, no teams found {"provider":"ringcentral"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Skip provider synchronisation, no teams found {"provider":"avaya"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Skip provider synchronisation, no teams found {"provider":"telus"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Skip provider synchronisation, no teams found {"provider":"salesloft"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Skip provider synchronisation, no teams found {"provider":"talkdesk"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Skip provider synchronisation, no teams found {"provider":"vonage"} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Done {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:sync-users","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9611cdc4-3487-48ac-a8be-abd396d6d642","trace_id":"240c1fec-a79e-403e-9d09-9359b3d94635"}
[2026-04-22 11:23:45] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf"} {"trace_id":"d4cfff0e-85bf-4b35-a8c0-d0752235ffc4","correlation_id":"36f4c5e4-2d3b-45d5-8822-374b2e6b4a0a"}
[2026-04-22 11:23:45] local.ERROR: [AskJiminnyReport:Generate] Error {"automatedReportUuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf","reportUuid":null,"code":0,"message":"Report not found"} {"trace_id":"d4cfff0e-85bf-4b35-a8c0-d0752235ffc4","correlation_id":"36f4c5e4-2d3b-45d5-8822-374b2e6b4a0a"}
[2026-04-22 11:24:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6d367001-e8e7-4716-b4c7-ecfc1e4099d7","trace_id":"3c503637-3f8c-49fc-81c9-20e6cab0d5cb"}
[2026-04-22 11:24:15] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"6d367001-e8e7-4716-b4c7-ecfc1e4099d7","trace_id":"3c503637-3f8c-49fc-81c9-20e6cab0d5cb"}
[2026-04-22 11:24:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6d367001-e8e7-4716-b4c7-ecfc1e4099d7","trace_id":"3c503637-3f8c-49fc-81c9-20e6cab0d5cb"}
[2026-04-22 11:24:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4f7594ad-5a0d-4699-9155-c3160f381090","trace_id":"f6e8ded8-51ba-4ced-a14e-3952376eb44e"}
[2026-04-22 11:24:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4f7594ad-5a0d-4699-9155-c3160f381090","trace_id":"f6e8ded8-51ba-4ced-a14e-3952376eb44e"}
[2026-04-22 11:24:28] local.NOTICE: Monitoring start {"correlation_id":"d53d718c-c5b0-4877-b185-778e70480387","trace_id":"f972d7ef-e3af-4148-b63d-29d07336642a"}
[2026-04-22 11:24:28] local.NOTICE: Monitoring end {"correlation_id":"d53d718c-c5b0-4877-b185-778e70480387","trace_id":"f972d7ef-e3af-4148-b63d-29d07336642a"}
[2026-04-22 11:24:35] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6a52569f-abd4-43cb-9b64-742d26d1a26d","trace_id":"caf645cd-7b0c-487f-bbb8-11e1bcc95065"}
[2026-04-22 11:24:35] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6a52569f-abd4-43cb-9b64-742d26d1a26d","trace_id":"caf645cd-7b0c-487f-bbb8-11e1bcc95065"}
[2026-04-22 11:24:39] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: Processing email batch 98404 for inbox 212 {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1354,"provider":"google"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1354,"provider":"google"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:39] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f965c2418cc","from":"Sentry <[EMAIL]>","to":"[EMAIL]","cc":null} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f965c2418cc","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f965c2418cc","message_id":"<[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f82c44aec62","from":"\"sonarqubecloud[bot]\" <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Review requested <[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f82c44aec62","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f82c44aec62","message_id":"<jiminny/app/pull/11980/[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f583c7902de","from":"\"claude[bot]\" <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Mention <[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f583c7902de","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f583c7902de","message_id":"<jiminny/app/pull/11894/[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f4ba64e9004","from":"Nikolay Yankov <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Mention <[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f4ba64e9004","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f4ba64e9004","message_id":"<jiminny/app/pull/11894/[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f4942d3e441","from":"Nikolay Yankov <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Push <[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f4942d3e441","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f4942d3e441","message_id":"<jiminny/app/pull/11894/before/3b2c08c0242d784595aa289c75cc8130d2d7e1c3/after/[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f404f5c4a18","from":"\"Aneliya Angelova (Jira)\" <[EMAIL]>","to":"Lukas Kovalik <[EMAIL]>","cc":null} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f404f5c4a18","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f404f5c4a18","message_id":"<[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3f3efdb3c372","from":"\"Aneliya Angelova (Jira)\" <[EMAIL]>","to":"Lukas Kovalik <[EMAIL]>","cc":null} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3f3efdb3c372","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3f3efdb3c372","message_id":"<[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3ebb8c0ab9a4","from":"Sentry <[EMAIL]>","to":"[EMAIL]","cc":null} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3ebb8c0ab9a4","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3ebb8c0ab9a4","message_id":"<[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3e73785f6383","from":"Vasil Vasilev <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Push <[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db3e73785f6383","team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db3e73785f6383","message_id":"<jiminny/app/pull/11980/before/b9b830afd523c589db27001a631f535843fa4cf5/after/[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: Processing an email from inbox batch {"batch":98404,"inbox_id":212,"email":"[EMAIL]","email_id":"19db3e6fa25b51a8","from":"Vasil Vasilev <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Push <[EMAIL]>"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"1e7be853-4d1e-4b7d-987d-88934978bf21","trace_id":"923b54b9-7e18-455a-b965-7e4327424fdc"}
[2026-04-22 11:24:43] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skip...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70845
|
|
70846
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
DMSActivityMorePhpStormcaltViewINavigareJiminny... v#* Channels# al-chapter# alerts# backend# c-learning-people# confusion-clinic# curiosity_lab# deal-insights-dev# engineering# frontend# general# infra-changes#jiminny-bg8 people-with-copilo…• people-with-zoom-….# platform-team# platform-tickets# product launches# random# releases# sofia-office# support# thank-yousi the neonle of fimi.• Direct messages.3 Aneliva Angelova, ...Nikolay VankoyCodeLaravelKeractorTOOIS# contusion-clinic8 36MessagesC Files• BookmarksMore7 reolies Last reply 1 day agoeorgi bavraktarov 10:58AM.Hey team.Joe from Dixa accidentallv aboliedan Al Scorecard to the wrong team and he'sasking if he can delete the scorecards. If heprovided us with a list of recordings, could wedelete the scorecards from our end?2 replies Last reply today at 11:20 AMGabriela Dureva 1:56 PMHev team. generated a loss Analvsis revort forthe CS team at Cognitive Credit and noticed itpicked un both renewal and uosellostopportunities. Is there a way to hiter the reportso it onlv looks ar onvortunines lost at renewallMessage #confusion-clinic+ AaWindowHelpFV faVsco.js v°9 JY-20157-AJ-report-not-send-notificationProject v© JiminnyDebugCommand.php X php api.php(C) RequestGenerateAskJiminnyReportJob.onp> D docsMtront-endiC AutomatedReportsService.onpC) RenortNotGenerated.ongreport-not-generated.blade.php> D lang>mnode modules llbrary root> D phpstan|> D publicv D resourcesv D viewsv@ emails> @ activities0 calendars•Jcrmpostmark-templatesv W reoorsask-liminny-report-generatreport-generated.blade.orrevort-not-generated,oladebutton.olade.ohoconterence-tooter.olade.ono•tooter.olade oho# sms.blade.ohotemplate.blade.php• Merrors I> notifications• M partials> shared>O vendorv Mroutesphp api.phpphp api_v2.phpphp console.onpphp customer_api.ongpnp embeadea.onpAДДAphp nealtn.pnppnp scim.onophp uprotected_web.phpphp web.phpphp webhook.php> O scripts.v D storage> M debuabarframework)v Dlogs.aitianoreaudio.wav= custom.loa=hubspot-journal-poll.logaravel lod139< nhnunit ymius ttt is= oauth-nrivate kevJatch succoccfullv annlied (todav 12-16Vclass JiminnyDebuqcommand extends commandA 1 7A 110 V3 лpublic const strina FREQUENCY WEEKLY ='weekly".3 usagespublic const string FREQUENCY_MONTHLY = 'monthly';2 usagespublic const string FREQUENCY_QUARTERLY = 'quarterly';2 usagespublic const string FREQUENCY_ONE_OFF = 'one_off';protected $signature = 'jiminny:debug';public function handle(JobDispatcherInterface SjobDispatcher, AutomatedReportsService SautomatedReportsServicSreport = AutomatedReportResult:: find(285);Siob = new RequestGenerateAskJiminnyReportJob(Sreport->getUuid•):SiobDispatcher->dispatch(Siob):exit(1"sthas->rormatuaresnoouzspatcher)*sthas->sendrarus7o0lzsoatcher. sautonatedrenortsservice:sthis->crmServiceo:sthis->aetPavloadSautomatedRenortsService):exitanprivate function crmServiceol...}private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsSenno usagesprivate function formatDate(JobDispatcherInterface SjobDispatcher): void{...}1 usagepublic function calculateFromAndToDatePeriod(strind strequency¿Carbon $fromDate = null?carbon stolate = null): array {...}100% C4Wed 22 Apr 14:32:13AskJiminnyReportActivityServiceTest -= custom.log= laravel.log X 4 SF jiminny@localhost]« HS_local [jiminny@localhost]# console lPKob.A console [STAGING]V.324 A[2026-04-22 11:23:08] local.INF0: Jiminny \Console\Commands\Command::run Memonusage berore scarting command 1"conL2020-04-2211.25.00LocaL.INru. Lemartscheaulel StAklinb bauch create 1"l."docker_Lamp_1" 1"correlac1on-10.'/12020-04-22 11.25.08 LocaL.LNFU.2026-04-2211:25:081[2026-04-2211:23:1012026-04-2211:25:101[2026-04-2211:23:111|2026-04-22[2026-04-2211:23:12112026-04-2211:23:15]11• 23:157[2026-04-2211:23:1512026-04-22 11:23:151[2026-04-22 11:23:15]12026-04-22 11•23•151[2026-04-22 11:23:15]12026-04-22 11-23-151[2026-04-22 11:23:15]2926-04-22 11-23-151[2026-04-22 11:23:15]Щ2004-04-9 11.22.151[2026-04-22 11:23:15][2026-04-22 11:23:15][2026-04-22 11:23:15][2026-04-22 11:23:15][2026-04-22 11:23:16][2026-04-22 11:23:16][2026-04-2211:23:161[2026-04-22 11:23:16]12026-04-2214:25:16112026-04-22 11:2511612026-04-2211:23:161[2026-04-2211:23:16]2026-04-2211:23:161[2026-04-2211:23:16]2026-04-2211: 23:161(2026-04-2211:23:16]12026-04-221[2026-04-2211:23:16]12026-04-22111•23•161|12026-04-22111:23:16]12926-04-22111•23•161[2026-04-22 11:23:16]Щ2004-04-29 11.22.141cmarlschedule. rinishtu bauch creace nLocaL.INFU: Jiminny Console Commands Command::run Memory usage for command "conJiminny Console Commands Command::run Memory usage betore starting command "command"Jiminny Console Commands Command::run Memory usage before startina command "command"mand: run Memory usage for command *"colAskJaminnvRenort.ceneratel Started "automatedRenortuuld":"e9165690-086c-4001-9407-5AskJiminnvReoort:Generatel Retry scheduled ""attemots":1> "correlation 1d"."6b94d07local.INFO: Jiminny \Console \Commands \Command::run Memory usage before starting command 1"commandSkan orovider svnchronisation.no teams found «"oroviden": "connect-and-sel", "correlocal.INFO: Start user synchronisation ("provider":"justcall","teams_count":1} {"correlation_id".local.INFO: Synchronising team {"provider":"justcall","team_id":1} {"correlation_id":"9611cdc4-34local.WARNING: [Salesforce] Account not connected for user {"userId":"cdf9285a-8ded-4a8b-bd7d-ec6[CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm.local.INF0: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider": "salesforceSocialAccoun+Servicel Token notrieved &"socialAccounttd".1500 "nnoviden"."calecfoncelocal.INF0: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":icamlwnonPocolvonl ToamMomhon £ound with sotivo enm connoction dllenm nnovidonll.lIcalodlocal.INF0: [SocialAccountService] Fetching token {"socialAccountId":1497,"provider":"justcall"}fcocialAccoun+Convicol Tokon notriovod SilcocialAccoun+Tdll.1107rovider":"justcall"}local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation id":[JustCall] Fetching usersa8be-abd396d6d642""local.INF0: [JustCall] Syncing external user {"userData":{"agent id":352648. "owner_id":352648. "fi[JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlatLocaL.INFU:Justlall Syncina external user ""userbata":"agent 1d:555694."owner. 10":352648,"†1[JustCall] Mapping to Jiminny user {"uremall":"7ettdp1npo1ntpayments.com"r "correuLOCOL.INFO"ustcauu suncino external user ""userbata":"agent 1d":5/5651,"ownen 10":552648."†1TJustCalll Mapping to Jiminny userNustCall Syncina external user ""*"agent 1d":373652."owner 1d":352648."F1[JustCall1 Mappina to Jiminny usensndpinoointoavments.com"> "correlatNustCalul Svncina external user "userdata":"agent 1d":390335."owner_ 1d":352648."f1[JustCall) Mapping to Jiminny use"bendoinoointoavments.com"> "correlaNustCall Suncina external user "serdata":"agent id":398216."owner_ 1d":352648."f1Hustcaul Mannina to Jiminny usenil":"[EMAIL]"} 1"correllustCall] Svncina extennal usen"userData"•f"agent id":410551 "ownen id".352648 "f:local. TNEO•[JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"corTocal. TNEO•IlustCall] Svncina external usen f"userlata"»f"agent id":449418 "ownen id".352648."4÷LlustCalll Manning to liminny usen {uusenfmai]"."sunnontAninnointnavments.com"} {"coIlustCalll Svncina external usen f"userlatal•f"agent id":449451 "ownen id".352648."£local.INF0: [JustCall] Mapping to Jiminny user {"userEmail":"[EMAIL]"} {"correlIison cvnchnonication comnloto dinnovidonll.lductcallll #toam idi.1 Itoaml.ldiminnylll &[2026-04-22 11:23:16]local.INF0: Skip provider synchronisation,1г2004-04-29 11.23•141|Jocol TNSh• Chin nnovidon evnchnonicationno teams found {"provider":"ringo[2026-04-22 11:23:16] local.INF0: Skip provider synchronisation, no teams found {"provider":"telus"} {"correlationid":[2026-04-22 11:23:16] local.INF0: Skip provider synchronisation,no teams found {"provider":"salesloft"} {"correlationio 4 space:...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70846
|
|
70881
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
[2026-04-22 11:33:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"fc4ddbfc-b6bb-47e2-892d-bda711b40218","trace_id":"ebb74068-a095-45ec-b7c8-f555c7bedbbf"}
[2026-04-22 11:33:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"fc4ddbfc-b6bb-47e2-892d-bda711b40218","trace_id":"ebb74068-a095-45ec-b7c8-f555c7bedbbf"}
[2026-04-22 11:33:13] local.NOTICE: Monitoring start {"correlation_id":"866819d3-f62e-415f-88e1-8479a01894d6","trace_id":"08095cf5-a1cc-4960-8c33-f2006735f4fa"}
[2026-04-22 11:33:13] local.NOTICE: Monitoring end {"correlation_id":"866819d3-f62e-415f-88e1-8479a01894d6","trace_id":"08095cf5-a1cc-4960-8c33-f2006735f4fa"}
[2026-04-22 11:33:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4994a737-d5de-4f4d-b853-c20f17e73cc3","trace_id":"b4ff711b-1a3e-4756-a492-6b7e84504442"}
[2026-04-22 11:33:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4994a737-d5de-4f4d-b853-c20f17e73cc3","trace_id":"b4ff711b-1a3e-4756-a492-6b7e84504442"}
[2026-04-22 11:33:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"64bdb7c0-d2f9-43ba-bf29-fa124a5470f6","trace_id":"cbaf4f2a-7f32-4249-8355-17759a7707fa"}
[2026-04-22 11:33:17] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"64bdb7c0-d2f9-43ba-bf29-fa124a5470f6","trace_id":"cbaf4f2a-7f32-4249-8355-17759a7707fa"}
[2026-04-22 11:33:17] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"64bdb7c0-d2f9-43ba-bf29-fa124a5470f6","trace_id":"cbaf4f2a-7f32-4249-8355-17759a7707fa"}
[2026-04-22 11:33:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"64bdb7c0-d2f9-43ba-bf29-fa124a5470f6","trace_id":"cbaf4f2a-7f32-4249-8355-17759a7707fa"}
[2026-04-22 11:33:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a0d06343-b165-429c-b33f-52e9359ac47f","trace_id":"5855fd1e-38ea-46a2-be25-79164275b57e"}
[2026-04-22 11:33:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a0d06343-b165-429c-b33f-52e9359ac47f","trace_id":"5855fd1e-38ea-46a2-be25-79164275b57e"}
[2026-04-22 11:34:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"03bf00b8-5aff-426f-945d-e5485c58c176","trace_id":"30ce31f4-2304-4f03-a25d-8718fa4f3c83"}
[2026-04-22 11:34:17] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"03bf00b8-5aff-426f-945d-e5485c58c176","trace_id":"30ce31f4-2304-4f03-a25d-8718fa4f3c83"}
[2026-04-22 11:34:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"03bf00b8-5aff-426f-945d-e5485c58c176","trace_id":"30ce31f4-2304-4f03-a25d-8718fa4f3c83"}
[2026-04-22 11:34:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ccd9b279-c007-413f-b343-bdb49e17efda","trace_id":"3d2cecbf-76ec-4cf9-a20f-b1dff970d85f"}
[2026-04-22 11:34:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ccd9b279-c007-413f-b343-bdb49e17efda","trace_id":"3d2cecbf-76ec-4cf9-a20f-b1dff970d85f"}
[2026-04-22 11:34:30] local.NOTICE: Monitoring start {"correlation_id":"61d660f3-2549-4696-80c9-c889513ae972","trace_id":"7688ea42-c126-4d70-a813-4f2667312e75"}
[2026-04-22 11:34:30] local.NOTICE: Monitoring end {"correlation_id":"61d660f3-2549-4696-80c9-c889513ae972","trace_id":"7688ea42-c126-4d70-a813-4f2667312e75"}
[2026-04-22 11:34:31] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"1543cc59-8771-4fe8-b6d8-843176953099","trace_id":"40c4e180-e093-476a-82b5-0a6b1afd3463"}
[2026-04-22 11:34:32] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"1543cc59-8771-4fe8-b6d8-843176953099","trace_id":"40c4e180-e093-476a-82b5-0a6b1afd3463"}
[2026-04-22 11:34:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3d4ad9d9-1587-4837-ba0b-c66eb70901ab","trace_id":"bad5001f-cd7c-42c9-819c-4b44cfadda40"}
[2026-04-22 11:34:33] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"3d4ad9d9-1587-4837-ba0b-c66eb70901ab","trace_id":"bad5001f-cd7c-42c9-819c-4b44cfadda40"}
[2026-04-22 11:34:33] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"3d4ad9d9-1587-4837-ba0b-c66eb70901ab","trace_id":"bad5001f-cd7c-42c9-819c-4b44cfadda40"}
[2026-04-22 11:34:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3d4ad9d9-1587-4837-ba0b-c66eb70901ab","trace_id":"bad5001f-cd7c-42c9-819c-4b44cfadda40"}
[2026-04-22 11:34:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8c1b1076-e669-4f51-867b-4b4c4d1bcfdf","trace_id":"0e7ac162-6708-403b-b36c-c22973c805f7"}
[2026-04-22 11:34:34] local.INFO: Running conference:monitor:count command for activities in (2026-04-22 11:32:00, 2026-04-22 11:34:00] {"correlation_id":"8c1b1076-e669-4f51-867b-4b4c4d1bcfdf","trace_id":"0e7ac162-6708-403b-b36c-c22973c805f7"}
[2026-04-22 11:34:34] local.INFO: [conference:monitor:count] No activities found in (2026-04-22 11:32:00, 2026-04-22 11:34:00] {"correlation_id":"8c1b1076-e669-4f51-867b-4b4c4d1bcfdf","trace_id":"0e7ac162-6708-403b-b36c-c22973c805f7"}
[2026-04-22 11:34:34] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8c1b1076-e669-4f51-867b-4b4c4d1bcfdf","trace_id":"0e7ac162-6708-403b-b36c-c22973c805f7"}
[2026-04-22 11:35:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"94b30a1d-3b4f-494c-a0f4-bd5cb135ae59","trace_id":"4d4d1148-c51e-4910-b802-7bfd5de35b88"}
[2026-04-22 11:35:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"94b30a1d-3b4f-494c-a0f4-bd5cb135ae59","trace_id":"4d4d1148-c51e-4910-b802-7bfd5de35b88"}
[2026-04-22 11:35:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"94b30a1d-3b4f-494c-a0f4-bd5cb135ae59","trace_id":"4d4d1148-c51e-4910-b802-7bfd5de35b88"}
[2026-04-22 11:35:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8568eb5e-c02a-4641-acd4-93492c224b60","trace_id":"2e2cd52b-d815-4037-acec-50f5ea7e2763"}
[2026-04-22 11:35:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8568eb5e-c02a-4641-acd4-93492c224b60","trace_id":"2e2cd52b-d815-4037-acec-50f5ea7e2763"}
[2026-04-22 11:35:06] local.NOTICE: Monitoring start {"correlation_id":"39b9fad7-15bc-4f1b-909a-e03f65571c7f","trace_id":"c463c9c4-2ac8-44b7-a69e-52524cb07768"}
[2026-04-22 11:35:06] local.NOTICE: Monitoring end {"correlation_id":"39b9fad7-15bc-4f1b-909a-e03f65571c7f","trace_id":"c463c9c4-2ac8-44b7-a69e-52524cb07768"}
[2026-04-22 11:35:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3793b561-6f1c-4668-bc43-42dafd77d815","trace_id":"c817a509-4a63-428c-81ea-163ee3d92af2"}
[2026-04-22 11:35:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3793b561-6f1c-4668-bc43-42dafd77d815","trace_id":"c817a509-4a63-428c-81ea-163ee3d92af2"}
[2026-04-22 11:35:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4124642b-c493-40dd-9dce-74dd6e5faa1f","trace_id":"60073c12-317b-4c09-8c0a-0180fb9873f2"}
[2026-04-22 11:35:08] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"4124642b-c493-40dd-9dce-74dd6e5faa1f","trace_id":"60073c12-317b-4c09-8c0a-0180fb9873f2"}
[2026-04-22 11:35:08] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"4124642b-c493-40dd-9dce-74dd6e5faa1f","trace_id":"60073c12-317b-4c09-8c0a-0180fb9873f2"}
[2026-04-22 11:35:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4124642b-c493-40dd-9dce-74dd6e5faa1f","trace_id":"60073c12-317b-4c09-8c0a-0180fb9873f2"}
[2026-04-22 11:35:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8c125d9e-9396-4f8a-adb7-301f760f54ab","trace_id":"e8e23d4a-306e-44de-8982-1dae007fcbe8"}
[2026-04-22 11:35:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8c125d9e-9396-4f8a-adb7-301f760f54ab","trace_id":"e8e23d4a-306e-44de-8982-1dae007fcbe8"}
[2026-04-22 11:35:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c61f0506-24de-405d-8dde-b20e629e4a5f","trace_id":"38946042-3e8e-49ad-b03e-d2cb1376334b"}
[2026-04-22 11:35:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c61f0506-24de-405d-8dde-b20e629e4a5f","trace_id":"38946042-3e8e-49ad-b03e-d2cb1376334b"}
[2026-04-22 11:35:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e934c410-1c58-4b20-be2d-0b9070ab4caf","trace_id":"2766e6d1-8d48-4d4c-9ccc-f8f5f79d8f2e"}
[2026-04-22 11:35:13] local.INFO: Running pre-meeting notification command {"correlation_id":"e934c410-1c58-4b20-be2d-0b9070ab4caf","trace_id":"2766e6d1-8d48-4d4c-9ccc-f8f5f79d8f2e"}
[2026-04-22 11:35:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e934c410-1c58-4b20-be2d-0b9070ab4caf","trace_id":"2766e6d1-8d48-4d4c-9ccc-f8f5f79d8f2e"}
[2026-04-22 11:35:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"aa78e7ee-9d13-4eaf-a176-69f866dac6c6","trace_id":"0ad47628-6fe2-4d8e-880b-cbb793df618f"}
[2026-04-22 11:35:14] local.INFO: Running conference:monitor:start command for activities in (2026-04-22 11:25:00, 2026-04-22 11:30:00] {"correlation_id":"aa78e7ee-9d13-4eaf-a176-69f866dac6c6","trace_id":"0ad47628-6fe2-4d8e-880b-cbb793df618f"}
[2026-04-22 11:35:14] local.INFO: [conference:monitor:start] No activities found in (2026-04-22 11:25:00, 2026-04-22 11:30:00] {"correlation_id":"aa78e7ee-9d13-4eaf-a176-69f866dac6c6","trace_id":"0ad47628-6fe2-4d8e-880b-cbb793df618f"}
[2026-04-22 11:35:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"aa78e7ee-9d13-4eaf-a176-69f866dac6c6","trace_id":"0ad47628-6fe2-4d8e-880b-cbb793df618f"}
[2026-04-22 11:35:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"64623b54-06b8-45df-8843-5633ca0803ed","trace_id":"b748e1b2-07b8-46db-803d-15468fbe372e"}
[2026-04-22 11:35:16] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"11:30","to":"11:35"} {"correlation_id":"64623b54-06b8-45df-8843-5633ca0803ed","trace_id":"b748e1b2-07b8-46db-803d-15468fbe372e"}
[2026-04-22 11:35:16] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"01:25","to":"01:30"} {"correlation_id":"64623b54-06b8-45df-8843-5633ca0803ed","trace_id":"b748e1b2-07b8-46db-803d-15468fbe372e"}
[2026-04-22 11:35:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"64623b54-06b8-45df-8843-5633ca0803ed","trace_id":"b748e1b2-07b8-46db-803d-15468fbe372e"}
[2026-04-22 11:35:17] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:17] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:18] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:18] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:18] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:18] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:18] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:18] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"21446861-d6ea-4761-ad7d-da1f19e1d243","trace_id":"dbc6c000-4a9f-4600-b60b-142ece70b077"}
[2026-04-22 11:35:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"f90e5f42-de58-49c5-9583-c139bce80f0a","trace_id":"8c3196a3-03a9-46d0-b8f7-2687f9d0a70c"}
[2026-04-22 11:35:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"67c1ab50-f8c5-4699-8758-00bb4c260e77","trace_id":"fda8b23c-4342-4771-9022-85dc7ec1ff5a"}
[2026-04-22 11:35:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"67c1ab50-f8c5-4699-8758-00bb4c260e77","trace_id":"fda8b23c-4342-4771-9022-85dc7ec1ff5a"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-22T11:37:20.269250Z"} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:20] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"f90e5f42-de58-49c5-9583-c139bce80f0a","trace_id":"8c3196a3-03a9-46d0-b8f7-2687f9d0a70c"}
[2026-04-22 11:35:20] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:25] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:25] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:25] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:30] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:30] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:31] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:46] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:46] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:35:46] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"5aa14db9-abd0-44ae-89dd-ee9385a6afa3","trace_id":"8e5d676d-5731-474e-befd-76a80caf8d18"}
[2026-04-22 11:36:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"f893e317-ef47-4f6f-af58-f8f152a7706f","trace_id":"1b5274e2-91bb-4e2d-9c8c-2b36627044e5"}
[2026-04-22 11:36:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"f893e317-ef47-4f6f-af58-f8f152a7706f","trace_id":"1b5274e2-91bb-4e2d-9c8c-2b36627044e5"}
[2026-04-22 11:36:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"f893e317-ef47-4f6f-af58-f8f152a7706f","trace_id":"1b5274e2-91bb-4e2d-9c8c-2b36627044e5"}
[2026-04-22 11:36:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"efab429c-f9ce-4547-ac61-5d982a02fa01","trace_id":"79baaa96-fd52-4a22-9006-fccc83125a9d"}
[2026-04-22 11:36:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"efab429c-f9ce-4547-ac61-5d982a02fa01","trace_id":"79baaa96-fd52-4a22-9006-fccc83125a9d"}
[2026-04-22 11:36:06] local.NOTICE: Monitoring start {"correlation_id":"5b3bc90b-ea92-4bab-9a6d-077a64446cbc","trace_id":"3c266e8d-4dff-4d5f-b968-d816fafd6b5b"}
[2026-04-22 11:36:06] local.NOTICE: Monitoring end {"correlation_id":"5b3bc90b-ea92-4bab-9a6d-077a64446cbc","trace_id":"3c266e8d-4dff-4d5f-b968-d816fafd6b5b"}
[2026-04-22 11:36:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2188404e-6c6f-4064-a682-cd7ba7bfb971","trace_id":"21a7cbf2-ea5f-4f31-ba70-b58bb2c7633a"}
[2026-04-22 11:36:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2188404e-6c6f-4064-a682-cd7ba7bfb971","trace_id":"21a7cbf2-ea5f-4f31-ba70-b58bb2c7633a"}
[2026-04-22 11:36:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"225089e6-d11c-48c3-ae18-e0b623ac03f6","trace_id":"df4fdcc8-0fae-42f2-90f6-8d6efa9def6d"}
[2026-04-22 11:36:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"225089e6-d11c-48c3-ae18-e0b623ac03f6","trace_id":"df4fdcc8-0fae-42f2-90f6-8d6efa9def6d"}
[2026-04-22 11:36:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"225089e6-d11c-48c3-ae18-e0b623ac03f6","trace_id":"df4fdcc8-0fae-42f2-90f6-8d6efa9def6d"}
[2026-04-22 11:36:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"225089e6-d11c-48c3-ae18-e0b623ac03f6","trace_id":"df4fdcc8-0fae-42f2-90f6-8d6efa9def6d"}
[2026-04-22 11:36:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"17904ac8-9f97-47b5-be48-2ab24219c8b7","trace_id":"6bdfdb53-1082-485e-89f4-50681dcd9edd"}
[2026-04-22 11:36:10] local.INFO: Running conference:monitor:count command for activities in (2026-04-22 11:34:00, 2026-04-22 11:36:00] {"correlation_id":"17904ac8-9f97-47b5-be48-2ab24219c8b7","trace_id":"6bdfdb53-1082-485e-89f4-50681dcd9edd"}
[2026-04-22 11:36:10] local.INFO: [conference:monitor:count] No activities found in (2026-04-22 11:34:00, 2026-04-22 11:36:00] {"correlation_id":"17904ac8-9f97-47b5-be48-2ab24219c8b7","trace_id":"6bdfdb53-1082-485e-89f4-50681dcd9edd"}
[2026-04-22 11:36:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"17904ac8-9f97-47b5-be48-2ab24219c8b7","trace_id":"6bdfdb53-1082-485e-89f4-50681dcd9edd"}
[2026-04-22 11:36:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"85cd334e-222d-42a1-8e24-c41f1c4d7af7","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:sync-hubspot-objects","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"85cd334e-222d-42a1-8e24-c41f1c4d7af7","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:12] local.INFO: [SyncHubspotObjects] Starting sync {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","usage":24770056,"real_usage":65011712,"pid":33688} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:12] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:12] local.INFO: [SocialAccountService] Token needs refreshing {"socialAccountId":1499,"provider":"hubspot"} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:12] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:12] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1499,"provider":"hubspot","refreshToken":"96f94c623a404e02ebdbf07f1b75707bb6cdbf848cbf45d418baf608c41a8d86","state":"connected"} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:13] local.INFO: [SocialAccountObserver] Saving model {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:13] local.INFO: [SocialAccountObserver] Access token was modified, encrypting {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4b92edcd-e0f5-4264-984b-0667558aa6e0","trace_id":"b9357a81-d470-439f-b1a0-a4dad108bc0f"}
[2026-04-22 11:36:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:notify-not-logged","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4b92edcd-e0f5-4264-984b-0667558aa6e0","trace_id":"b9357a81-d470-439f-b1a0-a4dad108bc0f"}
[2026-04-22 11:36:13] local.INFO: [SocialAccountService] Token refreshed {"socialAccountId":1499,"provider":"hubspot","state":"connected"} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:13] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"hubspot","crm_owner":148,"team_id":2} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:13] local.INFO: [HubSpot] Syncing opportunities using strategy: lastModified {"team":2} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [Hubspot] Pagination completed {"team_id":2,"endpoint":"https://api.hubapi.com/crm/v3/objects/deals/search","total_requests":1,"total_records_fetched":0,"total_elapsed_seconds":0.47,"average_seconds_per_request":0.47} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [HubSpot] Synced opportunities {"team":2,"strategies":"lastModified","sync_count":0,"total":0,"last_synced_id":null,"duration_ms":477.66} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [SyncHubspotObjects] Sync finished {"team":"abae74b8-bfa8-4383-9a7f-89f4bf2bdbb4","provider":"hubspot","status":"completed","duration_ms":1449.21,"usage":25215592,"real_usage":65011712,"pid":33688} {"correlation_id":"5d7455eb-8ce4-4e31-acb3-f23bc3c959f0","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [SyncHubspotObjects] Starting sync {"team":"b2b115eb-93ce-4d1b-929c-173757df8fba","usage":25256160,"real_usage":65011712,"pid":33688} {"correlation_id":"37961385-a988-44c6-9f91-edfb574827fd","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.WARNING: [HubSpot] Account not connected for user {"userId":"33e34a7a-1c02-4f04-87ac-22c3a385e6e3","account":{"Jiminny\\Models\\SocialAccount":{"id":306,"sociable_id":109,"provider_user_id":"11348452","expires":1701077403,"refresh_token_expires":null,"provider":"hubspot","state":"full-refresh","auth_scope":null,"retry_after":null,"created_at":"2020-09-01 16:59:04","updated_at":"2023-11-27 09:30:03"}}} {"correlation_id":"37961385-a988-44c6-9f91-edfb574827fd","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"hubspot","crm_owner":109,"team_id":29} {"correlation_id":"37961385-a988-44c6-9f91-edfb574827fd","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {"crm_provider":"hubspot","team_id":29} {"correlation_id":"37961385-a988-44c6-9f91-edfb574827fd","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {"crm_provider":"hubspot","team_id":29} {"correlation_id":"37961385-a988-44c6-9f91-edfb574827fd","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [SyncHubspotObjects] Sync finished {"team":"b2b115eb-93ce-4d1b-929c-173757df8fba","provider":"hubspot","status":"disconnected","duration_ms":21.81,"usage":25149544,"real_usage":65011712,"pid":33688,"reason":"Your HubSpot account has become disconnected. Please login to Jiminny to reconnect."} {"correlation_id":"37961385-a988-44c6-9f91-edfb574827fd","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [SyncHubspotObjects] Starting sync {"team":"b2d49a54-b645-4637-a7ae-a86cfce6e8e4","usage":25187912,"real_usage":65011712,"pid":33688} {"correlation_id":"e4aba023-f0b7-434f-b0af-35043818e267","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.WARNING: [HubSpot] Account not connected for user {"userId":"2ac0447f-3c8c-4ce0-baeb-b63ddb76fa9b","account":null} {"correlation_id":"e4aba023-f0b7-434f-b0af-35043818e267","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"hubspot","crm_owner":130,"team_id":42} {"correlation_id":"e4aba023-f0b7-434f-b0af-35043818e267","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {"crm_provider":"hubspot","team_id":42} {"correlation_id":"e4aba023-f0b7-434f-b0af-35043818e267","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] No team member found with active crm connection {"crm_provider":"hubspot","team_id":42} {"correlation_id":"e4aba023-f0b7-434f-b0af-35043818e267","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [SyncHubspotObjects] Sync finished {"team":"b2d49a54-b645-4637-a7ae-a86cfce6e8e4","provider":"hubspot","status":"disconnected","duration_ms":23.42,"usage":25124104,"real_usage":65011712,"pid":33688,"reason":"Social account for HubSpot cannot be found. Please login to Jiminny to connect."} {"correlation_id":"e4aba023-f0b7-434f-b0af-35043818e267","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [SyncHubspotObjects] Starting sync {"team":"c6b9d6b0-b48d-4832-a68c-a57d60651888","usage":25162472,"real_usage":65011712,"pid":33688} {"correlation_id":"5ee8fa6b-4e1a-4ba9-81af-7c252448970f","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.WARNING: [HubSpot] Account not connected for user {"userId":"71e3aac5-fb66-47c5-a236-2d051ae3e319","account":null} {"correlation_id":"5ee8fa6b-4e1a-4ba9-81af-7c252448970f","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] Integration owner is not connected, attempting team members {"crm_provider":"hubspot","crm_owner":256,"team_id":49} {"correlation_id":"5ee8fa6b-4e1a-4ba9-81af-7c252448970f","trace_id":"a061ffef-8d2c-4977-827a-a0866798d813"}
[2026-04-22 11:36:14] local.INFO: [CrmOwnerResolver] No team members found with active crm connection {"crm_provider":"hubspot","team_id":49} {"correlation_id":"5ee8fa6b-4e1a-4ba9-81af-7c252448970f","trace_id":"a061ffef-8d2c-4977-827a-a08...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70881
|
|
70889
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70889
|
|
70906
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LOG_PREFIX
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
10/16
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:37:44] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf"} {"correlation_id":"80ec3d57-0b9a-4ab1-9f9c-9990466e23e0","trace_id":"5c026b67-ced9-4fba-85bd-eafe4207a56b"}
[2026-04-22 11:37:45] local.ERROR: [AskJiminnyReport:Generate] Error {"automatedReportUuid":"e916569b-086c-4bd1-94d7-5e3802c27ccf","reportUuid":null,"code":0,"message":"Report not found"} {"correlation_id":"80ec3d57-0b9a-4ab1-9f9c-9990466e23e0","trace_id":"5c026b67-ced9-4fba-85bd-eafe4207a56b"}
[2026-04-22 11:37:45] local.INFO: [AskJiminnyReport:Generate] Retry scheduled {"attempts":1} {"correlation_id":"80ec3d57-0b9a-4ab1-9f9c-9990466e23e0","trace_id":"5c026b67-ced9-4fba-85bd-eafe4207a56b"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70906
|
|
70961
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelpX3docker$0• 885§ Support Daily • in 13 mA100% <7DOCKER• 881docker882-zsh* Build full day ac... • X4worker-es-update:worker-es-update_00:stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00:startedworker-calendar:worker-calendar_00:startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00:startedworker-crm-update:worker-crm-update_00: startedworker-download:worker-download_00: startedworker-emails:worker-emails_00:startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00: startedrootedocker_Lamp_1:/home/jiminny# php artisan jiminny: debugroot@docker_lamp_1:/home/jiminny# php artisan jiminny:debugroot@docker_lamp_1:/home/jiminny# php artisan jiminny:debugscreenpipe"-zsh86ErrorCall to a member function getUuid() on nullat app/Console/Commands/JiminnyDebugCommand.php: 3733public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService SautomatedReportsService): void3435Sreport = AutomatedReportResult: :find(71);363738$job = new RequestGenerateAskJiminnyReportJob(Sreport->getUuid());SjobDispatcher->dispatch($job);3940exit(1);41+13 vendor frames14artisan: 13Illuminate\Foundation\Application::handleCommand(Object(Symfony\Component\Console\Input\ArgvInput))root@docker_lamp_1:/home/jiminny# |APP (-zsh)87Wed 22 Apr 14:47:00T81ec2-user@ip-10-..• *8+...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70961
|
|
70962
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LOG_PREFIX
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
7/16
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
74
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:45:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c17a4b38-9ec6-46ff-9616-89b68dd1ff0c","trace_id":"0b2d04a1-9115-4b5a-91d2-f8d5faedae34"}
[2026-04-22 11:45:14] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"c17a4b38-9ec6-46ff-9616-89b68dd1ff0c","trace_id":"0b2d04a1-9115-4b5a-91d2-f8d5faedae34"}
[2026-04-22 11:45:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c17a4b38-9ec6-46ff-9616-89b68dd1ff0c","trace_id":"0b2d04a1-9115-4b5a-91d2-f8d5faedae34"}
[2026-04-22 11:45:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"78e3039f-24da-4108-a5af-394095b9cc8c","trace_id":"fe82551c-e325-446b-9dba-f56c8a194baf"}
[2026-04-22 11:45:25] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"78e3039f-24da-4108-a5af-394095b9cc8c","trace_id":"fe82551c-e325-446b-9dba-f56c8a194baf"}
[2026-04-22 11:45:30] local.ERROR: Call to a member function getUuid() on null {"exception":"[object] (Error(code: 0): Call to a member function getUuid() on null at /home/jiminny/app/Console/Commands/JiminnyDebugCommand.php:37)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Jiminny\\Console\\Commands\\JiminnyDebugCommand->handle(Object(Jiminny\\Jobs\\JobDispatcher), Object(Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService))
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Console/Command.php(211): Illuminate\\Container\\Container->call(Array)
#6 /home/jiminny/vendor/symfony/console/Command/Command.php(341): Illuminate\\Console\\Command->execute(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Console/Command.php(180): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#8 /home/jiminny/vendor/symfony/console/Application.php(1117): Illuminate\\Console\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#9 /home/jiminny/vendor/symfony/console/Application.php(356): Symfony\\Component\\Console\\Application->doRunCommand(Object(Jiminny\\Console\\Commands\\JiminnyDebugCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#10 /home/jiminny/vendor/symfony/console/Application.php(195): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(198): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1235): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#13 /home/jiminny/artisan(13): Illuminate\\Foundation\\Application->handleCommand(Object(Symfony\\Component\\Console\\Input\\ArgvInput))
#14 {main}
"} {"correlation_id":"c416ac30-2e54-49b0-8399-162924c9defc","trace_id":"c0ea3f08-6f47-42b2-[CREDIT_CARD]"}
[2026-04-22 11:45:35] local.NOTICE: Monitoring start {"correlation_id":"bd29c56d-bcaa-43b3-90c3-cb22f44008aa","trace_id":"b8a81599-b504-4c6d-9ae6-7092164b3479"}
[2026-04-22 11:45:36] local.NOTICE: Monitoring end {"correlation_id":"bd29c56d-bcaa-43b3-90c3-cb22f44008aa","trace_id":"b8a81599-b504-4c6d-9ae6-7092164b3479"}
[2026-04-22 11:45:43] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"a4cc6728-7691-40c7-a131-44913942f950","trace_id":"aeef144a-5995-43fd-9827-918da87b9171"}
[2026-04-22 11:45:44] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"a4cc6728-7691-40c7-a131-44913942f950","trace_id":"aeef144a-5995-43fd-9827-918da87b9171"}
[2026-04-22 11:45:51] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"e482840e-d688-49eb-bc8b-611a4221523e","trace_id":"fe36a2af-691c-4322-9b8f-7977f71dd447"}
[2026-04-22 11:45:51] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"e482840e-d688-49eb-bc8b-611a4221523e","trace_id":"fe36a2af-691c-4322-9b8f-7977f71dd447"}
[2026-04-22 11:45:51] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"e482840e-d688-49eb-bc8b-611a4221523e","trace_id":"fe36a2af-691c-4322-9b8f-7977f71dd447"}
[2026-04-22 11:45:51] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"e482840e-d688-49eb-bc8b-611a4221523e","trace_id":"fe36a2af-691c-4322-9b8f-7977f71dd447"}
[2026-04-22 11:46:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"adfa8430-c1a4-427b-a32a-bbaad5ad5879","trace_id":"e227ffa5-0581-4c74-96be-2ee7d8b60d48"}
[2026-04-22 11:46:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"adfa8430-c1a4-427b-a32a-bbaad5ad5879","trace_id":"e227ffa5-0581-4c74-96be-2ee7d8b60d48"}
[2026-04-22 11:46:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ff0ea10f-577d-4db3-a7b3-e7ef98ed5cbc","trace_id":"32688fee-68ef-4c3c-a1b3-ffc95e9337e3"}
[2026-04-22 11:46:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ff0ea10f-577d-4db3-a7b3-e7ef98ed5cbc","trace_id":"32688fee-68ef-4c3c-a1b3-ffc95e9337e3"}
[2026-04-22 11:46:24] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"efc58fbd-c1ec-40d3-a94a-a95d18383101","trace_id":"37318aa8-1099-4a32-b349-d68a89b1855b"}
[2026-04-22 11:46:24] local.INFO: Running pre-meeting notification command {"correlation_id":"efc58fbd-c1ec-40d3-a94a-a95d18383101","trace_id":"37318aa8-1099-4a32-b349-d68a89b1855b"}
[2026-04-22 11:46:24] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"efc58fbd-c1ec-40d3-a94a-a95d18383101","trace_id":"37318aa8-1099-4a32-b349-d68a89b1855b"}
[2026-04-22 11:46:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bc90e9a0-d8a3-44cc-ab4d-9b48726f8118","trace_id":"23667f9b-8f13-41cc-9d3a-2434e9395253"}
[2026-04-22 11:46:29] local.INFO: Running conference:monitor:start command for activities in (2026-04-22 11:36:00, 2026-04-22 11:41:00] {"correlation_id":"bc90e9a0-d8a3-44cc-ab4d-9b48726f8118","trace_id":"23667f9b-8f13-41cc-9d3a-2434e9395253"}
[2026-04-22 11:46:29] local.INFO: [conference:monitor:start] No activities found in (2026-04-22 11:36:00, 2026-04-22 11:41:00] {"correlation_id":"bc90e9a0-d8a3-44cc-ab4d-9b48726f8118","trace_id":"23667f9b-8f13-41cc-9d3a-2434e9395253"}
[2026-04-22 11:46:29] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bc90e9a0-d8a3-44cc-ab4d-9b48726f8118","trace_id":"23667f9b-8f13-41cc-9d3a-2434e9395253"}
[2026-04-22 11:46:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d2c62faf-c569-4286-abe7-bdcd8dc7da9c","trace_id":"4cfd1662-8a19-413a-a919-af2c6ea38b7d"}
[2026-04-22 11:46:33] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"11:41","to":"11:46"} {"correlation_id":"d2c62faf-c569-4286-abe7-bdcd8dc7da9c","trace_id":"4cfd1662-8a19-413a-a919-af2c6ea38b7d"}
[2026-04-22 11:46:33] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"01:36","to":"01:41"} {"correlation_id":"d2c62faf-c569-4286-abe7-bdcd8dc7da9c","trace_id":"4cfd1662-8a19-413a-a919-af2c6ea38b7d"}
[2026-04-22 11:46:33] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d2c62faf-c569-4286-abe7-bdcd8dc7da9c","trace_id":"4cfd1662-8a19-413a-a919-af2c6ea38b7d"}
[2026-04-22 11:46:41] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:41] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:41] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:41] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:42] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"44fd200c-d504-4795-b94d-d0f585a9c7c6","trace_id":"b3ce4324-030f-446e-ac2c-80bf39fc9eaf"}
[2026-04-22 11:46:53] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"10f042cc-bc24-44ee-bab9-70b287ca2fff","trace_id":"b8a53f72-b32b-460a-b9d4-762419ead58c"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-22T11:48:54.146418Z"} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Auth] Requesting new client credentials token {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:54] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"10f042cc-bc24-44ee-bab9-70b287ca2fff","trace_id":"b8a53f72-b32b-460a-b9d4-762419ead58c"}
[2026-04-22 11:46:54] local.INFO: [HubSpot Journal Auth] Successfully obtained new access token {"expires_in":1800,"cached_for":1500} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:46:55] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
Project
Project
New File or Directory…...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70962
|
|
70971
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
iTerm2ShellEditViewSessionScriptsProfilesWindowHelpSupport Daily - in 13 mA100% <7dockerDOCKER981docker882-zshX3* Build full day ac.…..• x4configcachecompiledeventsroutesviewsjiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00:stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00:stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00:stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00: stoppedworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedworker-download:worker-download_00: stoppedworker-nudges:worker-nudges_00: stoppedartisan-schedule:artisan-schedule_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker-calendar:worker-calendar_00: stoppedworker:worker_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-audio:worker-audio_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00:startedjiminny-worker-processing-3:jiminny-worker-processing-3_00:startedjiminny-worker-processing-4:jiminny-worker-processing-4_00:startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00:startedworker-download:worker-download_00:startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00:startedroot@docker_lamp_1:/home/jiminny# Uscreenpipe"• 885-zsh86APP (-zsh)13.96ms DONE32.17ms DONE5.53ms DONE4.15ms DONE3.63ms DONE16.07ms DONE87Wed 22 Apr 14:47:44181ec2-user@ip-10-..• *8|...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70971
|
|
70976
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
1
110
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReport::find(71);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
exit(1);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
34
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:47:51] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":38613,"workerId":"","target":"activities"} {"correlation_id":"91db8113-da6e-4341-9df9-f25163e64460","trace_id":"bad17f85-9df3-43f5-917f-ec18b064f115"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal Auth] Requesting new client credentials token {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal Auth] Successfully obtained new access token {"expires_in":1800,"cached_for":1500} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":58,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":475.9,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.72} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:54] local.ERROR: Class "Jiminny\Console\Commands\AutomatedReport" not found {"exception":"[object] (Error(code: 0): Class \"Jiminny\\Console\\Commands\\AutomatedReport\" not found at /home/jiminny/app/Console/Commands/JiminnyDebugCommand.php:35)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Jiminny\\Console\\Commands\\JiminnyDebugCommand->handle(Object(Jiminny\\Jobs\\JobDispatcher), Object(Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService))
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Console/Command.php(211): Illuminate\\Container\\Container->call(Array)
#6 /home/jiminny/vendor/symfony/console/Command/Command.php(341): Illuminate\\Console\\Command->execute(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Console/Command.php(180): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#8 /home/jiminny/vendor/symfony/console/Application.php(1117): Illuminate\\Console\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#9 /home/jiminny/vendor/symfony/console/Application.php(356): Symfony\\Component\\Console\\Application->doRunCommand(Object(Jiminny\\Console\\Commands\\JiminnyDebugCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#10 /home/jiminny/vendor/symfony/console/Application.php(195): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(198): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1235): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#13 /home/jiminny/artisan(13): Illuminate\\Foundation\\Application->handleCommand(Object(Symfony\\Component\\Console\\Input\\ArgvInput))
#14 {main}
"} {"correlation_id":"91853d4b-a5e3-4e68-acb1-2644bac2f3c2","trace_id":"9c169d65-c5e0-4d1e-81e5-6750393b46d5"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70976
|
|
70977
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
1
110
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReport::find(71);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
exit(1);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
34
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:47:51] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":38613,"workerId":"","target":"activities"} {"correlation_id":"91db8113-da6e-4341-9df9-f25163e64460","trace_id":"bad17f85-9df3-43f5-917f-ec18b064f115"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal Auth] Requesting new client credentials token {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:51] local.INFO: [HubSpot Journal Auth] Successfully obtained new access token {"expires_in":1800,"cached_for":1500} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":58,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":475.9,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.72} {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:52] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"4550a55c-9147-454f-98b5-e0fc9129b0ed","trace_id":"fdfe8148-5fc1-473f-b90a-aeb255cb0c3d"}
[2026-04-22 11:47:54] local.ERROR: Class "Jiminny\Console\Commands\AutomatedReport" not found {"exception":"[object] (Error(code: 0): Class \"Jiminny\\Console\\Commands\\AutomatedReport\" not found at /home/jiminny/app/Console/Commands/JiminnyDebugCommand.php:35)
[stacktrace]
#0 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Jiminny\\Console\\Commands\\JiminnyDebugCommand->handle(Object(Jiminny\\Jobs\\JobDispatcher), Object(Jiminny\\Services\\Kiosk\\AutomatedReports\\AutomatedReportsService))
#1 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Util.php(43): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#2 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(96): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
#3 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
#4 /home/jiminny/vendor/laravel/framework/src/Illuminate/Container/Container.php(799): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
#5 /home/jiminny/vendor/laravel/framework/src/Illuminate/Console/Command.php(211): Illuminate\\Container\\Container->call(Array)
#6 /home/jiminny/vendor/symfony/console/Command/Command.php(341): Illuminate\\Console\\Command->execute(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#7 /home/jiminny/vendor/laravel/framework/src/Illuminate/Console/Command.php(180): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
#8 /home/jiminny/vendor/symfony/console/Application.php(1117): Illuminate\\Console\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#9 /home/jiminny/vendor/symfony/console/Application.php(356): Symfony\\Component\\Console\\Application->doRunCommand(Object(Jiminny\\Console\\Commands\\JiminnyDebugCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#10 /home/jiminny/vendor/symfony/console/Application.php(195): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#11 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(198): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#12 /home/jiminny/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1235): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
#13 /home/jiminny/artisan(13): Illuminate\\Foundation\\Application->handleCommand(Object(Symfony\\Component\\Console\\Input\\ArgvInput))
#14 {main}
"} {"correlation_id":"91853d4b-a5e3-4e68-acb1-2644bac2f3c2","trace_id":"9c169d65-c5e0-4d1e-81e5-6750393b46d5"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70977
|
|
70997
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
1
110
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReport::find(71);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
exit(1);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
14
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:49:01] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":38932,"workerId":"","target":"activities"} {"correlation_id":"1426fc7c-a656-46c4-bdfc-17c424cb712a","trace_id":"971762c9-30b5-4778-a142-adb8978fda7c"}
[2026-04-22 11:49:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3782608a-ffcb-44b1-a981-3cc5df65bb41","trace_id":"2aba2fee-151f-48fa-a11f-fef9b262a433"}
[2026-04-22 11:49:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3782608a-ffcb-44b1-a981-3cc5df65bb41","trace_id":"2aba2fee-151f-48fa-a11f-fef9b262a433"}
[2026-04-22 11:49:10] local.NOTICE: Monitoring start {"correlation_id":"de28bfed-9c13-4fbe-95d2-1fe60b1dbe56","trace_id":"d4eefe3a-349c-4617-9cfe-4bab49f3db7a"}
[2026-04-22 11:49:10] local.NOTICE: Monitoring end {"correlation_id":"de28bfed-9c13-4fbe-95d2-1fe60b1dbe56","trace_id":"d4eefe3a-349c-4617-9cfe-4bab49f3db7a"}
[2026-04-22 11:49:10] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"b0e2d5f4-f0ae-4690-b65a-fd70caf57e17","trace_id":"ed584890-cd11-421a-bf30-b9f4fe278584"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70997
|
|
70998
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
1
110
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReport::find(71);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
exit(1);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
14
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:49:01] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":38932,"workerId":"","target":"activities"} {"correlation_id":"1426fc7c-a656-46c4-bdfc-17c424cb712a","trace_id":"971762c9-30b5-4778-a142-adb8978fda7c"}
[2026-04-22 11:49:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3782608a-ffcb-44b1-a981-3cc5df65bb41","trace_id":"2aba2fee-151f-48fa-a11f-fef9b262a433"}
[2026-04-22 11:49:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3782608a-ffcb-44b1-a981-3cc5df65bb41","trace_id":"2aba2fee-151f-48fa-a11f-fef9b262a433"}
[2026-04-22 11:49:10] local.NOTICE: Monitoring start {"correlation_id":"de28bfed-9c13-4fbe-95d2-1fe60b1dbe56","trace_id":"d4eefe3a-349c-4617-9cfe-4bab49f3db7a"}
[2026-04-22 11:49:10] local.NOTICE: Monitoring end {"correlation_id":"de28bfed-9c13-4fbe-95d2-1fe60b1dbe56","trace_id":"d4eefe3a-349c-4617-9cfe-4bab49f3db7a"}
[2026-04-22 11:49:10] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"b0e2d5f4-f0ae-4690-b65a-fd70caf57e17","trace_id":"ed584890-cd11-421a-bf30-b9f4fe278584"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70998
|
|
70999
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
1
110
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\Activity;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Activity\CrmOwnerResolver;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
/**
* Class JiminnyDebugCommand
*
* @package Jiminny\Console\Commands
*/
class JiminnyDebugCommand extends Command
{
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
protected $signature = 'jiminny:debug';
public function handle(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService): void
{
$report = AutomatedReport::find(71);
$job = new RequestGenerateAskJiminnyReportJob($report->getUuid());
$jobDispatcher->dispatch($job);
exit(1);
// $this->formatDate($jobDispatcher);
// $this->sendMail($jobDispatcher, $automatedReportsService);
// $this->crmService();
$this->getPayload($automatedReportsService);
exit(1);
}
private function crmService()
{
$activity = Activity::find(418141);
$team = Team::find(19);
$config = $team->getCrmConfiguration();
$crmResolver = app(CrmOwnerResolver::class, [
'team' => $team,
'integrationAdmin' => $team->getOwner(),
'providerSlug' => $config->getProviderName(),
]);
$crmService = $crmResolver->prepareCrmService();
$crmService->createTranscriptNotes($activity);
}
private function sendMail(JobDispatcherInterface $jobDispatcher, AutomatedReportsService $automatedReportsService)
{
$reportUuid = '';
// $report = $automatedReportsService->getReportResult($reportUuid);
$report = AutomatedReportResult::find(275);
$validRecipients = $automatedReportsService->getValidRecipientUsers(
$report->getReport(),
includeJiminny: true,
);
$recipient = $validRecipients[0];
$fileName = $automatedReportsService->getReportFileName($report);
$typeName = $report->getReport()->getCustomName()
?? $automatedReportsService->getReportTypeName($report);
$teamsName = $automatedReportsService->getReportTeamsName($report);
$periodName = $automatedReportsService->getReportPeriodName($report);
$s3Path = $automatedReportsService->getMediaPath($report);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$fileName ' . PHP_EOL . print_r($fileName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$typeName ' . PHP_EOL . print_r($typeName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$teamsName ' . PHP_EOL . print_r($teamsName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$periodName ' . PHP_EOL . print_r($periodName, true));
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$s3Path ' . PHP_EOL . print_r($s3Path, true));
$jobDispatcher->dispatch(
new SendReportMailJob(
reportUuid: $report->getUuid(),
s3Path: $s3Path,
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
fileName: $fileName,
typeName: $typeName,
teamsName: $teamsName,
periodName: $periodName,
isAskJiminny: true,
)
);
exit(1);
}
private function formatDate(JobDispatcherInterface $jobDispatcher): void
{
$customName = 'Custom report name';
// $frequency = self::FREQUENCY_DAILY;
// $frequency = self::FREQUENCY_WEEKLY;
$frequency = self::FREQUENCY_MONTHLY;
// $frequency = self::FREQUENCY_QUARTERLY;
// $frequency = self::FREQUENCY_ONE_OFF;
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
$periodName = $this->formatReportPeriodName($frequency, $from, $to);
$filenameSuffix = null;
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
$result = $this->sanitizeFileName("{$customName} - {$periodName}");
}
$this->info($result);
}
public function calculateFromAndToDatePeriod(
string $frequency,
?Carbon $fromDate = null,
?Carbon $toDate = null
): array {
if ($frequency === self::FREQUENCY_ONE_OFF) {
return [
'fromDate' => $fromDate,
'toDate' => $toDate,
];
}
$now = Carbon::now();
return match ($frequency) {
self::FREQUENCY_DAILY => [
'fromDate' => $now->copy()->subDay()->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_WEEKLY => [
'fromDate' => $now->copy()->subWeeks(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_MONTHLY => [
'fromDate' => $now->copy()->subMonths(1)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
self::FREQUENCY_QUARTERLY => [
'fromDate' => $now->copy()->subMonths(3)->startOfDay(),
'toDate' => $now->copy()->subDay()->endOfDay(),
],
default => throw new InvalidArgumentException("Unsupported frequency: {$frequency}"),
};
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
private function getPayload(AutomatedReportsService $automatedReportsService)
{
$reportResult = AutomatedReportResult::find(269);
$automatedReport = $reportResult->getReport();
$activityIds = [1,2,3];
$payload = $automatedReportsService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $reportResult,
activityIds: $activityIds,
);
\Illuminate\Support\Facades\Log::channel('custom_channel')->info('$payload ' . PHP_EOL . print_r($payload, true));
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
26
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:49:01] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":38932,"workerId":"","target":"activities"} {"correlation_id":"1426fc7c-a656-46c4-bdfc-17c424cb712a","trace_id":"971762c9-30b5-4778-a142-adb8978fda7c"}
[2026-04-22 11:49:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"5ef8a3b4-8767-4adf-bda5-41b277b387bf","trace_id":"f0c7fb88-a8fe-4dd1-b38c-99c72706f32f"}
[2026-04-22 11:49:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3782608a-ffcb-44b1-a981-3cc5df65bb41","trace_id":"2aba2fee-151f-48fa-a11f-fef9b262a433"}
[2026-04-22 11:49:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3782608a-ffcb-44b1-a981-3cc5df65bb41","trace_id":"2aba2fee-151f-48fa-a11f-fef9b262a433"}
[2026-04-22 11:49:10] local.NOTICE: Monitoring start {"correlation_id":"de28bfed-9c13-4fbe-95d2-1fe60b1dbe56","trace_id":"d4eefe3a-349c-4617-9cfe-4bab49f3db7a"}
[2026-04-22 11:49:10] local.NOTICE: Monitoring end {"correlation_id":"de28bfed-9c13-4fbe-95d2-1fe60b1dbe56","trace_id":"d4eefe3a-349c-4617-9cfe-4bab49f3db7a"}
[2026-04-22 11:49:10] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"b0e2d5f4-f0ae-4690-b65a-fd70caf57e17","trace_id":"ed584890-cd11-421a-bf30-b9f4fe278584"}
[2026-04-22 11:49:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"fdb6406d-3cda-4903-a266-24e931a5a1c2","trace_id":"444287ff-f8a3-4e8b-a23e-107365319aa0"}
[2026-04-22 11:49:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"fdb6406d-3cda-4903-a266-24e931a5a1c2","trace_id":"444287ff-f8a3-4e8b-a23e-107365319aa0"}
[2026-04-22 11:49:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"7c018f01-a592-4dcc-bff9-53ddfb998177","trace_id":"5b35e784-8771-4b74-9c63-17bddd9a7c50"}
[2026-04-22 11:49:13] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"7c018f01-a592-4dcc-bff9-53ddfb998177","trace_id":"5b35e784-8771-4b74-9c63-17bddd9a7c50"}
[2026-04-22 11:49:13] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"7c018f01-a592-4dcc-bff9-53ddfb998177","trace_id":"5b35e784-8771-4b74-9c63-17bddd9a7c50"}
[2026-04-22 11:49:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"7c018f01-a592-4dcc-bff9-53ddfb998177","trace_id":"5b35e784-8771-4b74-9c63-17bddd9a7c50"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
70999
|
|
71029
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LOG_PREFIX
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
3/16
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
// $this->dispatchNotGeneratedNotifications(
// $automatedReport,
// $reportService,
// $urlGenerator,
// $jobDispatcher,
// $logger,
// );
//
// return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
[2026-04-22 11:50:39] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c5a32cb8-f7cf-42fd-a843-869683b1a521","trace_id":"6ef6e9df-a25c-4353-a9cc-31c7dacf853d"}
[2026-04-22 11:50:40] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"c5a32cb8-f7cf-42fd-a843-869683b1a521","trace_id":"6ef6e9df-a25c-4353-a9cc-31c7dacf853d"}
[2026-04-22 11:50:40] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"c5a32cb8-f7cf-42fd-a843-869683b1a521","trace_id":"6ef6e9df-a25c-4353-a9cc-31c7dacf853d"}
[2026-04-22 11:50:40] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c5a32cb8-f7cf-42fd-a843-869683b1a521","trace_id":"6ef6e9df-a25c-4353-a9cc-31c7dacf853d"}
[2026-04-22 11:50:52] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"eded0333-5151-4558-a08f-44ca36036a81","trace_id":"15c70054-957b-4318-a727-03ffc08be843"}
[2026-04-22 11:50:52] local.INFO: Running conference:monitor:count command for activities in (2026-04-22 11:48:00, 2026-04-22 11:50:00] {"correlation_id":"eded0333-5151-4558-a08f-44ca36036a81","trace_id":"15c70054-957b-4318-a727-03ffc08be843"}
[2026-04-22 11:50:52] local.INFO: [conference:monitor:count] No activities found in (2026-04-22 11:48:00, 2026-04-22 11:50:00] {"correlation_id":"eded0333-5151-4558-a08f-44ca36036a81","trace_id":"15c70054-957b-4318-a727-03ffc08be843"}
[2026-04-22 11:50:52] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"eded0333-5151-4558-a08f-44ca36036a81","trace_id":"15c70054-957b-4318-a727-03ffc08be843"}
[2026-04-22 11:50:59] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"579d4548-c89c-4953-a682-8a8af43e1d18","trace_id":"267a7d62-f7bb-4334-8312-a8dd1998b8d5"}
[2026-04-22 11:50:59] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":60.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"579d4548-c89c-4953-a682-8a8af43e1d18","trace_id":"267a7d62-f7bb-4334-8312-a8dd1998b8d5"}
[2026-04-22 11:51:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"24b5164f-64e7-4a8a-af72-a359e016a04e","trace_id":"c7bc101a-4900-4de3-b87f-bd00505c05ba"}
[2026-04-22 11:51:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"24b5164f-64e7-4a8a-af72-a359e016a04e","trace_id":"c7bc101a-4900-4de3-b87f-bd00505c05ba"}
[2026-04-22 11:51:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"0e7189a1-1d69-40af-ba5f-7a3fa1b4134c","trace_id":"74433c61-968b-4ad4-8e20-96bff77e5289"}
[2026-04-22 11:51:12] local.INFO: Running pre-meeting notification command {"correlation_id":"0e7189a1-1d69-40af-ba5f-7a3fa1b4134c","trace_id":"74433c61-968b-4ad4-8e20-96bff77e5289"}
[2026-04-22 11:51:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"0e7189a1-1d69-40af-ba5f-7a3fa1b4134c","trace_id":"74433c61-968b-4ad4-8e20-96bff77e5289"}
[2026-04-22 11:51:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"05a0df33-8450-41bf-80c2-2ef8073e6895","trace_id":"9370080b-e8cf-4ee5-b43f-f652be428989"}
[2026-04-22 11:51:16] local.INFO: Running conference:monitor:start command for activities in (2026-04-22 11:41:00, 2026-04-22 11:46:00] {"correlation_id":"05a0df33-8450-41bf-80c2-2ef8073e6895","trace_id":"9370080b-e8cf-4ee5-b43f-f652be428989"}
[2026-04-22 11:51:16] local.INFO: [conference:monitor:start] No activities found in (2026-04-22 11:41:00, 2026-04-22 11:46:00] {"correlation_id":"05a0df33-8450-41bf-80c2-2ef8073e6895","trace_id":"9370080b-e8cf-4ee5-b43f-f652be428989"}
[2026-04-22 11:51:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"05a0df33-8450-41bf-80c2-2ef8073e6895","trace_id":"9370080b-e8cf-4ee5-b43f-f652be428989"}
[2026-04-22 11:51:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"753ad62d-63f6-4080-ad52-b5fdcca4685c","trace_id":"c065fffc-01ec-40e0-a883-ec7cde297aa5"}
[2026-04-22 11:51:17] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"11:46","to":"11:51"} {"correlation_id":"753ad62d-63f6-4080-ad52-b5fdcca4685c","trace_id":"c065fffc-01ec-40e0-a883-ec7cde297aa5"}
[2026-04-22 11:51:17] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"01:41","to":"01:46"} {"correlation_id":"753ad62d-63f6-4080-ad52-b5fdcca4685c","trace_id":"c065fffc-01ec-40e0-a883-ec7cde297aa5"}
[2026-04-22 11:51:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"753ad62d-63f6-4080-ad52-b5fdcca4685c","trace_id":"c065fffc-01ec-40e0-a883-ec7cde297aa5"}
[2026-04-22 11:51:19] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:19] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:20] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:20] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:20] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:20] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:20] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:20] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"3cf586dd-8787-4bfc-83ed-f272a4a6a317","trace_id":"afe31e15-d34a-4923-96a7-3e029fd5b45f"}
[2026-04-22 11:51:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"jiminny:transcription:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3b8df1c6-70cf-4ff1-bc9e-8457bf24968b","trace_id":"9f22738b-d61b-425c-a733-0a86f46368ad"}
[2026-04-22 11:51:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"93813116-d30e-4c3d-9450-cbf5d84fa86b","trace_id":"55bbc74d-1681-4bc8-a87a-4a87dbd0d4e3"}
[2026-04-22 11:51:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"jiminny:transcription:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3b8df1c6-70cf-4ff1-bc9e-8457bf24968b","trace_id":"9f22738b-d61b-425c-a733-0a86f46368ad"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-22T11:53:23.715208Z"} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Auth] Requesting new client credentials token {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"93813116-d30e-4c3d-9450-cbf5d84fa86b","trace_id":"55bbc74d-1681-4bc8-a87a-4a87dbd0d4e3"}
[2026-04-22 11:51:23] local.INFO: [HubSpot Journal Auth] Successfully obtained new access token {"expires_in":1800,"cached_for":1500} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:24] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:reset-governor","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"562970d5-9aea-4b26-9831-f82679d34d90","trace_id":"bea5f82e-e747-46fd-98d6-0c6af59df493"}
[2026-04-22 11:51:26] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:reset-governor","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"562970d5-9aea-4b26-9831-f82679d34d90","trace_id":"bea5f82e-e747-46fd-98d6-0c6af59df493"}
[2026-04-22 11:51:27] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3248586b-1a95-46c2-[CREDIT_CARD]","trace_id":"7ddc5338-2c3b-4e92-8231-4c11c0b33395"}
[2026-04-22 11:51:27] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3248586b-1a95-46c2-[CREDIT_CARD]","trace_id":"7ddc5338-2c3b-4e92-8231-4c11c0b33395"}
[2026-04-22 11:51:29] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:29] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:29] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:32] local.INFO: [Commands/AsyncUpdateEsEntities] Starting ES update worker {"pid":39384,"workerId":"","target":"activities"} {"correlation_id":"b8a85d59-7119-4fb5-95e4-cada7b18e301","trace_id":"1c24e227-36c1-45a7-b96d-d155ab6b2e42"}
[2026-04-22 11:51:34] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:34] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:34] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:49] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:49] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:51:49] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"0352ce16-8ead-4228-97db-0d50d5cb117a","trace_id":"22ec60d6-3c89-45b6-89c7-ef79fb0ab6f3"}
[2026-04-22 11:52:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"0352ce16-8ead-4228-97db-0d50d5cb117a","trace_id":"22ec60d6-3c89-45b6-89c7-ef79fb0ab6f3"}
[2026-04-22 11:52:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"0352ce16-8ead-4228-97db-0d50d5cb117a","trace_id":"22ec60d6-3c89-45b6-89c7-ef79fb0ab6f3"}
[2026-04-22 11:52:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9604df57-54e3-4041-b9d6-b3e6edf1956e","trace_id":"556b2930-4b96-478c-b524-1e68208f584b"}
[2026-04-22 11:52:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9604df57-54e3-4041-b9d6-b3e6edf1956e","trace_id":"556b2930-4b96-478c-b524-1e68208f584b"}
[2026-04-22 11:52:06] local.NOTICE: Monitoring start {"correlation_id":"05670b90-f087-4f95-805a-a8979cb20ff5","trace_id":"afab707b-58d8-4e06-aba8-9bf926b5c058"}
[2026-04-22 11:52:06] local.NOTICE: Monitoring end {"correlation_id":"05670b90-f087-4f95-805a-a8979cb20ff5","trace_id":"afab707b-58d8-4e06-aba8-9bf926b5c058"}
[2026-04-22 11:52:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"691ac711-b49c-400b-993d-bdfff83835e5","trace_id":"d1907a5d-d981-4b1e-bafc-4e45dee9b316"}
[2026-04-22 11:52:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"691ac711-b49c-400b-993d-bdfff83835e5","trace_id":"d1907a5d-d981-4b1e-bafc-4e45dee9b316"}
[2026-04-22 11:52:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3fe38156-8478-4ace-8d2f-b86b3ac9a895","trace_id":"ca26e562-2f4b-4dda-be3e-3f3bfe271bc6"}
[2026-04-22 11:52:08] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"3fe38156-8478-4ace-8d2f-b86b3ac9a895","trace_id":"ca26e562-2f4b-4dda-be3e-3f3bfe271bc6"}
[2026-04-22 11:52:08] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"3fe38156-8478-4ace-8d2f-b86b3ac9a895","trace_id":"ca26e562-2f4b-4dda-be3e-3f3bfe271bc6"}
[2026-04-22 11:52:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3fe38156-8478-4ace-8d2f-b86b3ac9a895","trace_id":"ca26e562-2f4b-4dda-be3e-3f3bfe271bc6"}
[2026-04-22 11:52:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"3abcae28-8d70-4234-a2f8-6dd9ef2d514f","trace_id":"de523403-8292-414e-917d-af3c22944d8f"}
[2026-04-22 11:52:09] local.INFO: Running conference:monitor:count command for activities in (2026-04-22 11:50:00, 2026-04-22 11:52:00] {"correlation_id":"3abcae28-8d70-4234-a2f8-6dd9ef2d514f","trace_id":"de523403-8292-414e-917d-af3c22944d8f"}
[2026-04-22 11:52:09] local.INFO: [conference:monitor:count] No activities found in (2026-04-22 11:50:00, 2026-04-22 11:52:00] {"correlation_id":"3abcae28-8d70-4234-a2f8-6dd9ef2d514f","trace_id":"de523403-8292-414e-917d-af3c22944d8f"}
[2026-04-22 11:52:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:count","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"3abcae28-8d70-4234-a2f8-6dd9ef2d514f","trace_id":"de523403-8292-414e-917d-af3c22944d8f"}
[2026-04-22 11:52:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9ad66f8f-95b6-462c-bdd9-47d854f98d87","trace_id":"7dff2613-6338-4d74-ad9f-c0d668ee0ccd"}
[2026-04-22 11:52:11] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"9ad66f8f-95b6-462c-bdd9-47d854f98d87","trace_id":"7dff2613-6338-4d74-ad9f-c0d668ee0ccd"}
[2026-04-22 11:52:11] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"9ad66f8f-95b6-462c-bdd9-47d854f98d87","trace_id":"7dff2613-6338-4d74-ad9f-c0d668ee0ccd"}
[2026-04-22 11:52:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9ad66f8f-95b6-462c-bdd9-47d854f98d87","trace_id":"7dff2613-6338-4d74-ad9f-c0d668ee0ccd"}
[2026-04-22 11:52:11] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 1 inboxes and created 0 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"02a96263-6303-4c1b-8d4b-3a3edf9f3b13","trace_id":"7dff2613-6338-4d74-ad9f-c0d668ee0ccd"}
[2026-04-22 11:52:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"86b8d390-7a2d-448b-b126-a056717167da","trace_id":"c74de3f7-4b9f-4fbe-a0c6-72c1e5f238b4"}
[2026-04-22 11:52:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"86b8d390-7a2d-448b-b126-a056717167da","trace_id":"c74de3f7-4b9f-4fbe-a0c6-72c1e5f238b4"}
[2026-04-22 11:52:19] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:19] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"https://api.hubapi.com/webhooks/v4/journal/latest"} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:19] local.INFO: [HubSpot Journal Polling] No data {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:19] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:19] local.WARNING: [HubSpot Journal Polling] Maximum empty results reached, stopping {"empty_results":5,"max_empty_results":5} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:19] local.INFO: [HubSpot Journal Polling] Service ending {"runtime_seconds":56,"total_cycles":5,"files_downloaded":0,"empty_files":0,"other_portal_skipped":0,"total_events":0,"events_per_file":0,"avg_api_ms":211.6,"avg_download_ms":0.0,"avg_transform_ms":0.0,"avg_process_ms":0.0,"peak_memory_mb":99.72} {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:52:19] local.INFO: [HubSpot Journal Polling] Released polling lock {"correlation_id":"dd3180e6-2e4e-4b18-a573-61463acd2aca","trace_id":"a51fb651-df0b-4860-865f-ded7036507c9"}
[2026-04-22 11:53:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9a9ea198-7060-4906-aabf-17ec2d47aca0","trace_id":"69846f0f-48c4-4b8f-b33f-7fa9f05158ba"}
[2026-04-22 11:53:04] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"9a9ea198-7060-4906-aabf-17ec2d47aca0","trace_id":"69846f0f-48c4-4b8f-b33f-7fa9f05158ba"}
[2026-04-22 11:53:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9a9ea198-7060-4906-aabf-17ec2d47aca0","trace_id":"69846f0f-48c4-4b8f-b33f-7fa9f05158ba"}
[2026-04-22 11:53:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c8de65d4-eec4-4195-a2dc-c3e0ae6c6bee","trace_id":"c351255f-8d0c-4eb1-ae96-c5f79e126828"}
[2026-04-22 11:53:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c8de65d4-eec4-4195-a2dc-c3e0ae6c6bee","trace_id":"c351255f-8d0c-4eb1-ae96-c5f79e126828"}
[2026-04-22 11:53:06] local.NOTICE: Monitoring start {"correlation_id":"a57e4683-baa2-42c3-b0b0-e22a5b4dd5cf","trace_id":"4c0c73ca-0646-4f90-927e-131fb72b592c"}
[2026-04-22 11:53:06] local.NOTICE: Monitoring end {"correlation_id":"a57e4683-baa2-42c3-b0b0-e22a5b4dd5cf","trace_id":"4c0c73ca-0646-4f90-927e-131fb72b592c"}
[2026-04-22 11:53:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2c658c21-b9c4-46e2-8073-6b119aa5bdff","trace_id":"58018826-a603-4a05-a0cd-8af020af5dec"}
[2026-04-22 11:53:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2c658c21-b9c4-46e2-8073-6b119aa5bdff","trace_id":"58018826-a603-4a05-a0cd-8af020af5dec"}
[2026-04-22 11:53:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"c151a197-6e9e-4a2e-b30f-a1a3495c36a9","trace_id":"910be553-4bc0-4613-ac55-aecf4c35fe19"}
[2026-04-22 11:53:09] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"c151a197-6e9e-4a2e-b30f-a1a3495c36a9","trace_id":"910be553-4bc0-4613-ac55-aecf4c35fe19"}
[2026-04-22 11:53:09] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"c151a197-6e9e-4a2e-b30f-a1a3495c36a9","trace_id":"910be553-4bc0-4613-ac55-aecf4c35fe19"}
[2026-04-22 11:53:09] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"c151a197-6e9e-4a2e-b30f-a1a3495c36a9","trace_id":"910be553-4bc0-4613-ac55-aecf4c35fe19"}
[2026-04-22 11:53:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d08aa9eb-9ae4-4929-85bc-1ad292c19cb0","trace_id":"3b9cd371-12de-4410-9c9a-4b4bf5607656"}
[2026-04-22 11:53:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:retry-failed","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"d08aa9eb-9ae4-4929-85bc-1ad292c19cb0","trace_id":"3b9cd371-12de-4410-9c9a-4b4bf5607656"}
[2026-04-22 11:54:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"7038f895-6e90-4d89-a1e0-62b9650dc5a6","trace_id":"186cb590-8fa8-4554-ae9f-7980b350479f"}
[2026-04-22 11:54:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"7038f895-6e90-4d89-a1e0-62b9650dc5a6","trace_id":"186cb590-8fa8-4554-ae9f-7980b350479f"}
[2026-04-22 11:54:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"7038f895-6e90-4d89-a1e0-62b9650dc5a6","trace_id":"186cb590-8fa8-4554-ae9f-7980b350479f"}
[2026-04-22 11:54:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"0d7bf19a-f825-4770-b36b-15d210bde0e2","trace_id":"049b40ac-846a-4bab-b0d9-b8f4b91f90bd"}
[2026-04-22 11:54:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"0d7bf19a-f825-4770-b36b-15d210bde0e2","trace_id":"049b40ac-846a-4bab-b0d9-b8f4b91f90bd"}
[2026-04-22 11:54:05] local.NOTICE: Monitoring start {"correlation_id":"2f02d47e-c25e-419b-a37a-6ffd3e7d6605","trace_id":"adb8db14-900d-4728-b9ed-2370a214409f"}
[2026-04-22 11:54:05] local.NOTICE: Monitoring end {"correlation_id":"2f02d47e-c25e-419b-a37a-6ffd3e7d6605","trace_id":"adb8db14-900d-4728-b9ed-2370a214409f"}
[2026-04-22 11:54:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"b0cd6157-f5d6-4a25-a7be-df8f32e80a0e","trace_id":"b9914249-2186-4080-b8ec-937abb227afd"}
[2026-04-22 11:54:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"b0cd6157-f5d6-4a25-a7be-df8f32e80a0e","trace_id":"b9914249-2186-4080-b8ec-937abb227afd"}
[2026-04-22 11:54:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6f0e8171-7b27-4b76-b4e6-86a49bc3eca5","trace_id":"47259a2f-824b-4005-ae3c-664736bd9c99"}
[2026-04-22 11:54:08] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"6f0e8171-7b27-4b76-b4e6-86a49bc3eca5","trace_id":"47259a2f-824b-4005-ae3c-664736bd9c99"}
[2026-04-22 11:54:08] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"6f0e8171-7b27-4b76-b4e6-86a49bc3eca5","trace_id":"47259a2f-824b-4005-ae3c-664736bd9c99"}
[2026-04-22 11:54:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6f0e8171-7b27-4b76-b4e6-86a49bc3eca5","trace_id":"47259a2f-824b-4005-ae3c-664736bd9c99"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71029
|
|
71055
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LOG_PREFIX
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
3/16
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
// $this->dispatchNotGeneratedNotifications(
// $automatedReport,
// $reportService,
// $urlGenerator,
// $jobDispatcher,
// $logger,
// );
//
// return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
232
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 11:54:16] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"92271b1f-d433-43d3-a5c1-24bc2cb18fe1","trace_id":"f173b553-e675-4ba6-9f1e-edc6a000c2af"}
[2026-04-22 11:54:16] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"92271b1f-d433-43d3-a5c1-24bc2cb18fe1","trace_id":"f173b553-e675-4ba6-9f1e-edc6a000c2af"}
[2026-04-22 11:54:16] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1496,"provider":"aircall"} {"correlation_id":"92271b1f-d433-43d3-a5c1-24bc2cb18fe1","trace_id":"f173b553-e675-4ba6-9f1e-edc6a000c2af"}
[2026-04-22 11:54:16] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"92271b1f-d433-43d3-a5c1-24bc2cb18fe1","trace_id":"f173b553-e675-4ba6-9f1e-edc6a000c2af"}
[2026-04-22 11:54:17] local.ERROR: [Aircall] Re-activating webhooks failed {"team_id":1,"reason":"{\"message\":\"Forbidden\"}"} {"correlation_id":"92271b1f-d433-43d3-a5c1-24bc2cb18fe1","trace_id":"f173b553-e675-4ba6-9f1e-edc6a000c2af"}
[2026-04-22 11:54:17] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:aircall:check-and-renew","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"92271b1f-d433-43d3-a5c1-24bc2cb18fe1","trace_id":"f173b553-e675-4ba6-9f1e-edc6a000c2af"}
[2026-04-22 11:54:23] local.INFO: [RetryFailedDownloads] Starting {"options":{"from":null,"to":null,"help":false,"silent":false,"quiet":false,"verbose":false,"version":false,"ansi":null,"no-interaction":false,"env":null}} {"correlation_id":"b08499ae-0f75-4f5c-a77d-7ce99c5b5e33","trace_id":"d685901a-f476-4199-8440-8cae7e41006b"}
[2026-04-22 11:55:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"9fbeb029-612e-4678-a3b8-3aba95cb155f","trace_id":"562f9843-4f0d-4bc3-80cd-78ef4acdd1ce"}
[2026-04-22 11:55:06] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"9fbeb029-612e-4678-a3b8-3aba95cb155f","trace_id":"562f9843-4f0d-4bc3-80cd-78ef4acdd1ce"}
[2026-04-22 11:55:06] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"9fbeb029-612e-4678-a3b8-3aba95cb155f","trace_id":"562f9843-4f0d-4bc3-80cd-78ef4acdd1ce"}
[2026-04-22 11:55:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4ff48fa4-df7a-4736-9545-279e7a48b229","trace_id":"cf1ea642-f6f3-426c-af0c-68f33c934c04"}
[2026-04-22 11:55:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4ff48fa4-df7a-4736-9545-279e7a48b229","trace_id":"cf1ea642-f6f3-426c-af0c-68f33c934c04"}
[2026-04-22 11:55:08] local.NOTICE: Monitoring start {"correlation_id":"93f2b45e-5a47-42c5-a2a3-59e91c4591e4","trace_id":"da47967f-01d1-4364-8013-e274ddcc00dd"}
[2026-04-22 11:55:08] local.NOTICE: Monitoring end {"correlation_id":"93f2b45e-5a47-42c5-a2a3-59e91c4591e4","trace_id":"da47967f-01d1-4364-8013-e274ddcc00dd"}
[2026-04-22 11:55:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"7c90fb0f-e94c-41ce-932c-f51e5ada1c10","trace_id":"c28c32ca-f451-4f65-880a-9e5d2370fff4"}
[2026-04-22 11:55:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"7c90fb0f-e94c-41ce-932c-f51e5ada1c10","trace_id":"c28c32ca-f451-4f65-880a-9e5d2370fff4"}
[2026-04-22 11:55:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"38ad48b4-112a-49aa-b9a5-3c555178ddce","trace_id":"3666e737-7f59-45d4-8d4b-ada54c758f56"}
[2026-04-22 11:55:11] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"38ad48b4-112a-49aa-b9a5-3c555178ddce","trace_id":"3666e737-7f59-45d4-8d4b-ada54c758f56"}
[2026-04-22 11:55:11] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"38ad48b4-112a-49aa-b9a5-3c555178ddce","trace_id":"3666e737-7f59-45d4-8d4b-ada54c758f56"}
[2026-04-22 11:55:11] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"38ad48b4-112a-49aa-b9a5-3c555178ddce","trace_id":"3666e737-7f59-45d4-8d4b-ada54c758f56"}
[2026-04-22 11:55:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"97e9c78b-c3aa-40a7-88a0-790ad3d61fe1","trace_id":"9e793fa6-110e-41df-8f71-ddd9d281995d"}
[2026-04-22 11:55:12] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"activity:purge-stale","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"97e9c78b-c3aa-40a7-88a0-790ad3d61fe1","trace_id":"9e793fa6-110e-41df-8f71-ddd9d281995d"}
[2026-04-22 11:55:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"5c575d1e-5243-4819-bbe7-fedb7deeb62c","trace_id":"66f58dc6-0ec5-48d2-9804-cb4cc051897a"}
[2026-04-22 11:55:14] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:text-relay:sync","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"5c575d1e-5243-4819-bbe7-fedb7deeb62c","trace_id":"66f58dc6-0ec5-48d2-9804-cb4cc051897a"}
[2026-04-22 11:55:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"cb67f605-9623-4a16-85ca-8eda7dfb089e","trace_id":"924293a9-587b-4292-8544-9501aeaae5a6"}
[2026-04-22 11:55:15] local.INFO: Running pre-meeting notification command {"correlation_id":"cb67f605-9623-4a16-85ca-8eda7dfb089e","trace_id":"924293a9-587b-4292-8544-9501aeaae5a6"}
[2026-04-22 11:55:15] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:pre-meeting-notification","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"cb67f605-9623-4a16-85ca-8eda7dfb089e","trace_id":"924293a9-587b-4292-8544-9501aeaae5a6"}
[2026-04-22 11:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"6ed634b1-76ba-4c53-8688-7e6f5e771e41","trace_id":"1b495a7d-10cb-45e3-8b94-b05905d41364"}
[2026-04-22 11:55:19] local.INFO: Running conference:monitor:start command for activities in (2026-04-22 11:45:00, 2026-04-22 11:50:00] {"correlation_id":"6ed634b1-76ba-4c53-8688-7e6f5e771e41","trace_id":"1b495a7d-10cb-45e3-8b94-b05905d41364"}
[2026-04-22 11:55:19] local.INFO: [conference:monitor:start] No activities found in (2026-04-22 11:45:00, 2026-04-22 11:50:00] {"correlation_id":"6ed634b1-76ba-4c53-8688-7e6f5e771e41","trace_id":"1b495a7d-10cb-45e3-8b94-b05905d41364"}
[2026-04-22 11:55:19] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:start","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"6ed634b1-76ba-4c53-8688-7e6f5e771e41","trace_id":"1b495a7d-10cb-45e3-8b94-b05905d41364"}
[2026-04-22 11:55:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"16c85e99-9192-426c-8ec0-1bd507e58530","trace_id":"29fda8c4-6689-4b36-ac2d-7b8dfb85ae7c"}
[2026-04-22 11:55:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesEnded {"from":"11:50","to":"11:55"} {"correlation_id":"16c85e99-9192-426c-8ec0-1bd507e58530","trace_id":"29fda8c4-6689-4b36-ac2d-7b8dfb85ae7c"}
[2026-04-22 11:55:23] local.INFO: conference:monitor:end:Jiminny\Console\Commands\Activities\MonitorMeetingEndCommand::logActivitiesWithUnfinishedSession {"from":"01:45","to":"01:50"} {"correlation_id":"16c85e99-9192-426c-8ec0-1bd507e58530","trace_id":"29fda8c4-6689-4b36-ac2d-7b8dfb85ae7c"}
[2026-04-22 11:55:23] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"conference:monitor:end","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"16c85e99-9192-426c-8ec0-1bd507e58530","trace_id":"29fda8c4-6689-4b36-ac2d-7b8dfb85ae7c"}
[2026-04-22 11:55:29] local.NOTICE: Repairing HubSpot tokens start {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.INFO: Trying to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":59,"provider":"hubspot","refreshToken":"97b78f6e2cc49965c00c2492b602b02708b1392551e6b3f113fbaa48992af90b","state":"full-refresh"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.ERROR: Failed to refresh HubSpot token {"account_id":59,"updated_at":"2025-10-03 09:32:05","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.INFO: Trying to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:29] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":306,"provider":"hubspot","refreshToken":"6fa6aa8cc641d131231acc3470f5c03cb3b07b2e580fb18f8acb3b1dbb72549b","state":"full-refresh"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:30] local.ERROR: Failed to refresh HubSpot token {"account_id":306,"updated_at":"2023-11-27 09:30:03","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:30] local.INFO: Trying to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:30] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:30] local.INFO: [SocialAccountService] Refreshing token from provider {"socialAccountId":1372,"provider":"hubspot","refreshToken":"9aa73948c761da29dce46c177cf9aee1fde483a44169ca38723f9f0597d7a8c4","state":"full-refresh"} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:30] local.ERROR: Failed to refresh HubSpot token {"account_id":1372,"updated_at":"2025-10-02 14:47:06","reason":"missing or invalid refresh token","previous":""} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:30] local.NOTICE: Repairing HubSpot tokens end {"total":3,"fixed":0,"failed":3} {"correlation_id":"3403ed55-4a13-43f3-8ec8-8cba4a5f5e23","trace_id":"aa3ec00c-ab54-4d57-96d5-9c1ac2514a43"}
[2026-04-22 11:55:41] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"89b23b6c-a8e3-4843-ba6a-8aa7259cd085","trace_id":"fd854117-678e-4a37-921c-d201fbe222d3"}
[2026-04-22 11:55:41] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"conference:pre-meeting-reminder","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"d9d61f9e-e4ef-4db1-84bc-4898b3eb7914","trace_id":"e106b95b-2a0b-4b2c-b431-7ee86ef3f898"}
[2026-04-22 11:55:41] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"crm:bullhorn:ping","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"89b23b6c-a8e3-4843-ba6a-8aa7259cd085","trace_id":"fd854117-678e-4a37-921c-d201fbe222d3"}
[2026-04-22 11:55:42] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"840ca577-0d69-450a-9bc5-083bed15b21c","trace_id":"4092053e-743b-4196-b59e-864130f86337"}
[2026-04-22 11:55:42] local.INFO: [HubSpot Journal Command] Starting polling service {"correlation_id":"840ca577-0d69-450a-9bc5-083bed15b21c","trace_id":"4092053e-743b-4196-b59e-864130f86337"}
[2026-04-22 11:55:42] local.INFO: [HubSpot Journal Polling] Service starting {"memory_limit":"256M","max_execution_time":"0","initial_memory_mb":62.0} {"correlation_id":"840ca577-0d69-450a-9bc5-083bed15b21c","trace_id":"4092053e-743b-4196-b59e-864130f86337"}
[2026-04-22 11:55:42] local.INFO: [HubSpot Journal Polling] Acquired polling lock {"expires_at":"2026-04-22T11:57:42.160268Z"} {"correlation_id":"840ca577-0d69-450a-9bc5-083bed15b21c","trace_id":"4092053e-743b-4196-b59e-864130f86337"}
[2026-04-22 11:55:42] local.INFO: [HubSpot Journal Polling] Getting offset from database {"offset":"","jiminny_team_id":1} {"correlation_id":"840ca577-0d69-450a-9bc5-083bed15b21c","trace_id":"4092053e-743b-4196-b59e-864130f86337"}
[2026-04-22 11:55:42] local.INFO: [HubSpot Journal API] Fetching latest journal entry {"url":"[URL_WITH_CREDENTIALS] {"correlation_id":"2cfe28d9-5fd4-49bb-b21e-48788b2fe7b2","trace_id":"a910f965-c156-4c13-9670-a6a864f504cb"}
[2026-04-22 11:57:21] local.INFO: [Gmail] imported 14 emails via fu...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71055
|
|
71056
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LOG_PREFIX
New Line
Match Case
iTerm2ShellEditViewSessionScriptsProfilesWindowHelp•DOCKER• ₴1docker882-zshX3* Build full day ac...configcachecompiledeventsroutesviewsworker-analytics:worker-analytics_00: stoppedworker-crm-update:worker-crm-update_00: stoppedjiminny-worker-processing-2:jiminny-worker-processing-2_00:stoppedjiminny-worker-processing-3:jiminny-worker-processing-3_00:stoppedjiminny-worker-processing-4:jiminny-worker-processing-4_00:stoppedjiminny-worker-processing-5:jiminny-worker-processing-5_00:stoppedjiminny-worker-processing-delayed: jiminny-worker-processing-delayed_00: stoppedworker-nudges:worker-nudges_00: stoppedworker-download:worker-download_00: stoppedworker-calendar:worker-calendar_00:stoppedworker-emails:worker-emails_00: stoppedjiminny-worker-processing-1:jiminny-worker-processing-1_00: stoppedworker:worker_00: stoppedworker-audio:worker-audio_00: stoppedworker-conferences:worker-conferences_00: stoppedworker-crm-sync:worker-crm-sync_00: stoppedworker-es-update:worker-es-update_00: stoppedartisan-schedule:artisan-schedule_00: stoppedartisan-schedule:artisan-schedule_00: startedjiminny-worker-processing-1:jiminny-worker-processing-1_00: startedjiminny-worker-processing-2:jiminny-worker-processing-2_00: startedjiminny-worker-processing-3:jiminny-worker-processing-3_00: startedjiminny-worker-processing-4:jiminny-worker-processing-4_00: startedjiminny-worker-processing-5:jiminny-worker-processing-5_00: startedjiminny-worker-processing-delayed:jiminny-worker-processing-delayed_00:startedworker:worker_00: startedworker-analytics:worker-analytics_00: startedworker-audio:worker-audio_00: startedworker-calendar:worker-calendar_00: startedworker-conferences:worker-conferences_00: startedworker-crm-sync:worker-crm-sync_00: startedworker-crm-update:worker-crm-update_00: startedworker-download:worker-download_00:startedworker-emails:worker-emails_00: startedworker-es-update:worker-es-update_00: startedworker-nudges:worker-nudges_00:startedroot@docker_lamp_1:/home/jiminny#php artisan automated-reports --report-id 71lSupport Daily - in 1 m100% CWed 22 Apr 14:59:30docker• X4screenpipe"O 85Support Dailyin 1m - 15:00-15:15= Notes - Support Daily.10.52ms DONE5.64ms DONE12.56ms DONE20.84ms DONEC Join Google Meet...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71056
|
|
71573
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
102
3
34
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\ActivitySearch\FilterDefinition\InputTypeEnum;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Throwable;
class AutomatedReportsService
{
public const string TYPE_LOSS_ANALYSIS = 'loss_analysis';
public const string TYPE_ASK_JIMINNY = 'ask_jiminny';
/**
* Standard report types (used by kiosk for existing automated reports).
*/
// @TODO this will add filter, however if we need to control feature by FF we need conditional logic
public const array TYPES = [
['id' => 'exec_summary', 'name' => 'Exec Summary'],
['id' => 'coaching_profiles', 'name' => 'Coaching Profiles'],
['id' => 'product_feedback', 'name' => 'Product Feedback'],
['id' => self::TYPE_LOSS_ANALYSIS, 'name' => 'Loss Analysis'],
// ['id' => 'questions', 'name' => 'Questions'],
// ['id' => 'statistical_quant', 'name' => 'Statistical Quantitative'],
];
public const array ALL_TYPES = [
...self::TYPES,
['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'],
];
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
/**
* Frequencies for standard (non-Ask Jiminny) reports.
*/
public const array FREQUENCIES = [
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
/**
* Frequencies for Ask Jiminny reports.
*/
public const array ASK_JIMINNY_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
];
public const string MEDIA_TYPE_PDF = 'pdf';
public const string MEDIA_TYPE_PODCAST = 'podcast';
public const array MEDIA_TYPES = [self::MEDIA_TYPE_PDF, self::MEDIA_TYPE_PODCAST];
public const array MEDIA_TYPE_OBJECT_PDF = ['id' => self::MEDIA_TYPE_PDF, 'name' => 'PDF'];
public const array MEDIA_TYPE_OBJECT_PODCAST = ['id' => self::MEDIA_TYPE_PODCAST, 'name' => 'Podcast'];
public const array MEDIA_TYPE_OBJECTS = [self::MEDIA_TYPE_OBJECT_PDF, self::MEDIA_TYPE_OBJECT_PODCAST];
public const array CALL_TYPE_CONFERENCE = ['id' => 'conference', 'name' => 'Conference'];
public const array CALL_TYPE_DIALER = ['id' => 'dialer', 'name' => 'Dialer'];
public const int SENT_REPORT_AT_HOURS = 5;
public const string PDF_KEY = 'pdf';
public const string AUDIO_KEY = 'audio';
private const array ALL_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
private const string S3_DIR = 'reports';
private const array FILE_EXTENSIONS_VARIANTS = ['html', 'MD', 'pdf'];
private const array FILE_PODCAST_EXTENSIONS_VARIANTS = ['json', 'mp3', 'ssml'];
public function __construct(
private readonly TeamRepository $teamRepository,
private readonly GroupRepository $groupRepository,
private readonly UserRepository $userRepository,
private readonly StageRepository $stageRepository,
private readonly DealStagesService $dealStagesService,
private readonly RecipientsService $recipientsService,
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly Webhook $webhookService,
private readonly BusDispatcher $dispatcher,
private readonly ActivityTypeService $activityTypeService,
private readonly PlaybookCategoryRepository $playbookCategoryRepository,
private readonly AskAnythingPromptService $askAnythingPromptService,
private readonly SearchRepository $activitySearchRepository,
private readonly AskAnythingRepository $askAnythingRepository,
) {
}
public static function getTypes(): array
{
$types = self::TYPES;
return array_map(static function ($type) {
return $type['id'];
}, $types);
}
public static function getCallTypes(): array
{
return array_map(static function ($callType) {
return $callType['id'];
}, [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER]);
}
public static function getFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::FREQUENCIES);
}
// front-facing structure
public function getReportEnabledFieldData(bool $value = false): array
{
return [
'id' => 'report_enabled',
'label' => '',
'inputType' => InputTypeEnum::TOGGLE,
'value' => $value,
];
}
// Organizations = Teams
public function getOrganizationFieldData(?string $value = null, bool $shortVersion = false): array
{
$options = $this->getTeams();
if ($shortVersion) {
return [
'id' => 'organization',
'label' => 'Organization',
'options' => $options,
];
}
return [
'id' => 'organization',
'label' => 'Organization',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $options,
'value' => $value,
'dependencies' => [
'teams',
'deal_stage_at_call',
'current_deal_stage',
'recipients',
ActivityTypeService::PLAYBOOK_CATEGORIES_KEY,
],
'dependsOn' => [],
];
}
// Teams = Groups
public function getTeamFieldData(array $options = [], array $value = [], bool $shortVersion = false): array
{
if ($shortVersion) {
return [
'id' => 'teams',
'label' => 'Team',
'options' => $options,
];
}
return [
'id' => 'teams',
'label' => 'Team',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => false,
'placeholder' => 'Select',
'options' => $options,
'value' => $value, // value should be an array of objects {id, name}
'dependencies' => [ActivityTypeService::PLAYBOOK_CATEGORIES_KEY],
'dependsOn' => [],
];
}
public function getReportTypeFieldData(?string $value = null, bool $shortVersion = false, ?Team $team = null): array
{
$types = [];
if ($team instanceof Team) {
if ($team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
$types = self::TYPES;
}
if ($team->hasFeature(FeatureEnum::ASK_JIMINNY_REPORTS)) {
$types[] = ['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'];
}
} else {
$types = self::TYPES;
}
if ($shortVersion) {
return [
'id' => 'report_type',
'label' => 'Report Type',
'options' => $types,
];
}
return [
'id' => 'report_type',
'label' => 'Report Type',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $types,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getFrequencyFieldData(?string $value = null): array
{
return [
'id' => 'frequency',
'label' => 'Frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => self::FREQUENCIES,
'value' => $value,
'dependencies' => ['period'],
'dependsOn' => [],
];
}
public function getPeriodFieldData(?string $valueStartDate = null, ?string $valueEndDate = null): array
{
return [
'id' => 'period',
'label' => 'Select one-off period',
'inputType' => InputTypeEnum::DATE_RANGE,
'required' => true,
'placeholder' => 'Select',
'value' => ['startDate' => $valueStartDate, 'endDate' => $valueEndDate],
'queryParams' => [
'startDate' => 'start_date_period',
'endDate' => 'end_date_period',
],
'dependencies' => [],
'dependsOn' => ['frequency'],
];
}
public function getActivityTypesFieldData(?Team $team = null, array $value = [], array $teamsFilter = []): array
{
return $this->activityTypeService->getActivityTypeFieldData(team: $team, value: $value, groupIds: $teamsFilter);
}
public function getDealStageAtCallFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getDealStageAtCallFieldData(team: $team, value: $value);
}
public function getCurrentDealStageFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getCurrentDealStageFieldData(team: $team, value: $value);
}
public function getDealValueFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'deal_value',
'label' => 'Deal Value',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_deal_value',
'max' => 'max_deal_value',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallTypeFieldData(bool $conferenceOn = false, bool $dialerOn = false): array
{
$value = [];
$conferenceOn && $value[] = self::CALL_TYPE_CONFERENCE;
$dialerOn && $value[] = self::CALL_TYPE_DIALER;
return [
'id' => 'call_type',
'label' => 'Call Type',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => [
self::CALL_TYPE_CONFERENCE,
self::CALL_TYPE_DIALER,
],
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getMediaTypeFieldData(?AutomatedReport $report = null): array
{
$value = [];
if ($report) {
$value = $this->transformMediaTypes($report);
}
return [
'id' => 'media_types',
'label' => 'Export as',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => self::MEDIA_TYPE_OBJECTS,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallDurationFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'call_duration',
'label' => 'Call Duration',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_call_duration',
'max' => 'max_call_duration',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getRecipientsFieldData(?Team $team = null, array $value = []): array
{
return $this->recipientsService->getRecipientsFieldData(team: $team, value: $value);
}
public function getJiminnyRecipientsFieldData(array $value = []): array
{
return $this->recipientsService->getJiminnyRecipientsFieldData($value);
}
public function getAdditionalPromptInputFieldData(?string $value = null): array
{
return [
'id' => 'additional_prompt_input',
'label' => 'Special requirements',
'inputType' => InputTypeEnum::TEXTAREA,
'required' => false,
'placeholder' => 'What should be the focus of the report?',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCustomReportNameFieldData(?string $value = null): array
{
return [
'id' => 'custom_name',
'label' => 'Custom report name',
'inputType' => InputTypeEnum::TEXT,
'required' => false,
'placeholder' => 'Enter custom name',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
// data providers
public function getTeams(): array
{
$teams = $this->teamRepository->getTeamsForKiosk(status: Team::STATUS_ACTIVE);
$teamData = [];
foreach ($teams as $team) {
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
continue;
}
$teamData[] = $this->transformTeam($team);
}
return $teamData;
}
public function getTeamGroups(string $teamUuid): array
{
$data = [];
$team = $this->getTeam($teamUuid);
if ($team !== null) {
$groups = $team->groups()->get();
foreach ($groups as $group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
];
}
}
return $data;
}
public function getTeamsGroupsOptions(array $filterTeamUuids = []): array
{
$data = [];
$teams = $this->getTeams();
foreach ($teams as $team) {
if (! empty($filterTeamUuids) && ! in_array($team['id'], $filterTeamUuids, true)) {
continue;
}
$data[] = [
'label' => $team['name'],
'groups' => $this->getTeamGroups($team['id']),
];
}
return $data;
}
public function getTeam(string $teamUuid): ?Team
{
return $this->teamRepository->idOrUuid($teamUuid);
}
public function getTeamById(int $teamId): ?Team
{
return $this->teamRepository->find($teamId);
}
public function getGroupsUuids(AutomatedReport $report): array
{
$uuids = [];
$reportGroups = $report->getGroups();
foreach ($reportGroups as $groupId) {
if ($group = $this->groupRepository->find($groupId)) {
$uuids[] = $group->getUuid();
}
}
return $uuids;
}
public function getPlaybookCategoriesUuids(AutomatedReport $report): array
{
$uuids = [];
$playbookCategories = $report->getPlaybookCategories();
foreach ($playbookCategories as $id) {
if ($category = $this->playbookCategoryRepository->find($id)) {
$uuids[] = $category->getUuid();
}
}
return $uuids;
}
public function getDealAtCallStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getDealAtCallStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getCurrentDealStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getCurrentDealStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getRecipients());
}
public function getJiminnyUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function extractUserUuids(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => $user->getUuid())
->values()
->all();
}
// get mail data
public function getRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getRecipients());
}
/**
* @return array<UserContract>
*/
public function getRecipientUserObjects(AutomatedReport $report): array
{
$userIds = $report->getRecipients()['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->values()
->all();
}
private function getJiminnyRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function buildRecipientUsers(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => [
'email' => $user->getEmailAddress(),
'name' => $user->getName(),
'timezone' => $user->getTimezone()->getName(),
])
->values()
->all();
}
public function getValidRecipientUsers(AutomatedReport $report, bool $includeJiminny = false): array
{
if ($report->isAskJiminnyReport()) {
$recipients = $this->resolveAskJiminnyRecipients($report);
} else {
$recipients = $this->getRecipientUsers($report);
if ($includeJiminny) {
$recipients = array_merge($recipients, $this->getJiminnyRecipientUsers($report));
}
}
$emails = [];
return array_values(array_filter(
$recipients,
static function ($recipient) use (&$emails) {
if (empty($recipient['email']) || in_array($recipient['email'], $emails, true)) {
return false;
}
$emails[] = $recipient['email'];
return true;
}
));
}
private function resolveAskJiminnyRecipients(AutomatedReport $report): array
{
$recipients = [];
$creator = $report->getCreator();
if ($creator !== null) {
$recipients[] = [
'email' => $creator->getEmailAddress(),
'name' => $creator->getName(),
'timezone' => $creator->getTimezone()->getName(),
];
}
return array_merge(
$recipients,
$this->buildRecipientUsers($report->getRecipients()),
$this->getGroupRecipientUsers($report),
);
}
private function getGroupRecipientUsers(AutomatedReport $report): array
{
$users = [];
foreach ($report->getGroups() as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group === null) {
continue;
}
foreach ($group->getMembers() as $member) {
$users[] = [
'email' => $member->getEmailAddress(),
'name' => $member->getName(),
'timezone' => $member->getTimezone()->getName(),
];
}
}
return $users;
}
public function getReportTypeName(AutomatedReportResult $report): string
{
$type = $report->getReport()->getType();
$getType = $this->transformReportType($type);
return $getType['name'];
}
public function getReportPeriodName(AutomatedReportResult $report): string
{
$from = $report->getFromDate();
$to = $report->getToDate();
$frequency = $report->getReport()->getFrequency();
if ($from === null || $to === null) {
if (! $report->getReport()->isAskJiminnyReport()) {
$invalidPeriod = $from === null ? 'from' : 'to';
throw new ApplicationException('Report period is invalid: ' . $invalidPeriod);
}
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
}
return $this->formatReportPeriodName($frequency, $from, $to);
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function getReportTeamsName(AutomatedReportResult $report): string
{
$groups = $report->getGroups();
if (empty($groups)) {
return 'All';
}
// Get group names from repository
$groupNames = [];
foreach ($groups as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group) {
$groupNames[] = $group->getName();
}
}
if (count($groupNames) === 1) {
// Single team format
$teamsName = $groupNames[0];
} else {
// Multiple teams format
$teamsName = implode(', ', $groupNames);
}
return $teamsName;
}
public function getReportFileName(AutomatedReportResult $report): string
{
$customName = $report->getReport()->getCustomName();
$periodName = $this->getReportPeriodName($report);
$filenameSuffix = $this->getFilenameSuffix($report);
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$customName} - {$periodName}");
}
$baseName = $this->getReportTypeName($report);
if ($filenameSuffix) {
$baseName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$baseName} - {$periodName} - {$this->getReportTeamsName($report)}");
}
public function getReportFileNameWithExtension(AutomatedReportResult $result): string
{
$extension = $this->getMediaTypeMetadata($result)['extension'];
return $this->getReportFileName($result) . '.' . $extension;
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
public function isUserRecipientOfReport(User $user, AutomatedReport $report): bool
{
$recipientIds = array_map('intval', $report->getRecipients()['users'] ?? []);
if (in_array($user->getId(), $recipientIds, true)) {
return true;
}
if ($report->isAskJiminnyReport()) {
$groupId = $user->getGroupId();
if ($groupId !== null && in_array($groupId, $report->getGroups(), true)) {
return true;
}
}
return false;
}
public function transformReportResults(Collection $automatedReportResults): array
{
$data = [];
foreach ($automatedReportResults as $automatedReportResult) {
/** @var AutomatedReportResult $automatedReportResult */
$report = $automatedReportResult->getReport();
$createdBy = $report->getCreator();
$creator = [
'id' => $createdBy?->getUuid(),
'name' => $createdBy?->getName(),
'email' => $createdBy?->getEmailAddress(),
'photoUrl' => $createdBy?->getPhotoUrl(),
];
$data[] = [
'id' => $automatedReportResult->getUuid(),
'name' => $automatedReportResult->getName(),
'frequency' => $this->transformFrequency($report->getFrequency()),
'recipients' => $this->buildRecipients($report),
'report_type' => $this->transformReportType($report->getType()),
'media_type' => $automatedReportResult->getMediaType(),
'downloadUrl' => $this->generateReportResultDownloadUrl($automatedReportResult),
'viewUrl' => $this->generateReportResultViewUrl($automatedReportResult),
'generated_at' => $automatedReportResult->getGeneratedAt()?->toIso8601String(),
'creator' => $creator,
];
}
return $data;
}
private function buildRecipients(AutomatedReport $report): array
{
$creatorUuid = $report->getCreator()?->getUuid();
$recipients = array_values(array_filter(
$this->transformRecipients($report->getRecipients()),
static fn (array $recipient): bool => $recipient['id'] !== $creatorUuid,
));
if (! $report->isAskJiminnyReport()) {
return $recipients;
}
return [
...array_values($this->transformGroups(team: $report->getTeam(), groupsIds: $report->getGroups())),
...$recipients,
];
}
public function hasCallTypeConference(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_CONFERENCE['id'], $report->getCallTypes(), true);
}
public function hasCallTypeDialer(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_DIALER['id'], $report->getCallTypes(), true);
}
// transformers
private function transformTeam(Team $team): array
{
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
return [];
}
return [
'id' => $team->getUuid(),
'name' => $team->getName(),
];
}
private function transformReportFullView(AutomatedReport $report): array
{
$base = $this->transformReportBase($report);
return $report->getType() === self::TYPE_ASK_JIMINNY
? $base + $this->transformAskJiminnyFields($report)
: $base + $this->transformStandardReportFields($report);
}
private function transformReportBase(AutomatedReport $report): array
{
return [
'id' => $report->getUuid(),
'organization' => $this->transformOrganization(team: $report->getTeam()),
'report_type' => $this->transformReportType($report->getType()),
'frequency' => $this->transformFrequency($report->getFrequency()),
];
}
private function transformStandardReportFields(AutomatedReport $report): array
{
$team = $report->getTeam();
return [
'report_enabled' => $report->getStatus(),
'start_date_period' => $report->getFrom()?->format('Y-m-d H:i:s'),
'end_date_period' => $report->getTo()?->format('Y-m-d H:i:s'),
'deal_value_min' => $report->getDealValueMin(),
'deal_value_max' => $report->getDealValueMax(),
'call_types' => $this->transformCallType($report->getCallTypes()),
'media_types' => $this->transformMediaTypes($report),
'call_duration_min' => $this->transformDurationToMinutes($report->getCallDurationMin()),
'call_duration_max' => $this->transformDurationToMinutes($report->getCallDurationMax()),
'teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'deal_at_call_stages' => $this->transformStages(team: $team, stagesIds: $report->getDealAtCallStages()),
'current_deal_stages' => $this->transformStages(team: $team, stagesIds: $report->getCurrentDealStages()),
'recipients' => $this->transformRecipients($report->getRecipients()),
'created_by' => $this->transformCreator($report->getCreator()),
'additional_prompt_input' => $report->getAdditionalPromptInput(),
'custom_name' => $report->getCustomName(),
'created_at' => $report->getCreatedAt()->format('Y-m-d H:i:s'),
'updated_at' => $report->getUpdatedAt()->format('Y-m-d H:i:s'),
'deleted_at' => $report->getDeletedAt()?->format('Y-m-d H:i:s'),
];
}
private function transformAskJiminnyFields(AutomatedReport $report): array
{
$team = $report->getTeam();
$creatorId = $report->getAttribute('created_by');
$explicitUserIds = array_values(array_filter(
$report->getRecipients()['users'] ?? [],
static fn ($id) => $id !== $creatorId
));
return [
'report_name' => $report->getCustomName(),
'enabled' => $report->getStatus(),
'share_teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'share_users' => $this->transformRecipients(['users' => $explicitUserIds]),
'saved_search' => $this->transformSafeSearch($report->getSavedSearch()),
'ask_jiminny_prompt' => $this->transformAskJiminnyPrompt($report->getAskAnythingPrompt()),
'expires_on' => $report->getExpiresAt()?->format('Y-m-d'),
];
}
private function transformOrganization(?Team $team): array
{
return [
'id' => $team?->getUuid(),
'name' => $team?->getName(),
];
}
private function transformReportType(string $type): array
{
foreach (self::ALL_TYPES as $typeItem) {
if ($typeItem['id'] === $type) {
return $typeItem;
}
}
return [];
}
private function transformCallType(array $types): array
{
$result = [];
$callTypes = [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER];
foreach ($types as $type) {
foreach ($callTypes as $callTypeItem) {
if ($callTypeItem['id'] === $type) {
$result[] = $callTypeItem;
break;
}
}
}
return $result;
}
private function transformMediaTypes(AutomatedReport $report): array
{
$values = [];
foreach ($report->getMediaTypes() as $mediaType) {
if (! in_array($mediaType, self::MEDIA_TYPES, true)) {
continue;
}
$values[] = match ($mediaType) {
self::MEDIA_TYPE_PDF => self::MEDIA_TYPE_OBJECT_PDF,
self::MEDIA_TYPE_PODCAST => self::MEDIA_TYPE_OBJECT_PODCAST,
};
}
return $values;
}
private function transformFrequency(string $frequency): array
{
foreach (self::ALL_FREQUENCIES as $frequencyItem) {
if ($frequencyItem['id'] === $frequency) {
return $frequencyItem;
}
}
return [];
}
public function transformDurationToMinutes(?int $duration): ?int
{
if (! $duration) {
return null;
}
return (int) ($duration / 60);
}
private function transformGroups(?Team $team, array $groupsIds): array
{
if (empty($groupsIds) || ! $team) {
return [];
}
$data = [];
foreach ($groupsIds as $groupId) {
$group = $team->groups()->where('id', $groupId)->first();
if ($group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
'photoUrl' => $group->getPhotoUrl(),
];
}
}
return $data;
}
private function transformStages(?Team $team, array $stagesIds): array
{
if (empty($stagesIds) || ! $team) {
return [];
}
$data = [];
foreach ($stagesIds as $stageId) {
$stage = $team->stages()->where('id', $stageId)->first();
if ($stage) {
$data[] = [
'id' => $stage->getUuid(),
'name' => $stage->getName(),
];
}
}
return $data;
}
private function transformRecipients(array $recipients): array
{
$users = [];
foreach ($recipients['users'] ?? [] as $userId) {
$users[] = $this->transformUser($userId);
}
return $users;
}
private function transformCreator(?User $user): ?array
{
if ($user === null) {
return null;
}
return $this->transformUser($user->getId());
}
private function transformAskJiminnyPrompt(?AskAnythingPrompt $prompt): ?array
{
if ($prompt === null) {
return null;
}
return [
'id' => $prompt->getUuid(),
'name' => $prompt->getTitle(),
];
}
private function transformSafeSearch(?Search $search): ?array
{
if ($search === null) {
return null;
}
return [
'id' => $search->getUuid(),
'name' => $search->getName(),
];
}
private function transformUser(int $userId): array
{
/* @var ?User $user */
$user = $this->userRepository->find($userId);
return [
'id' => $user?->getUuid(),
'name' => $user?->getName(),
'email' => $user?->getEmailAddress(),
'photoUrl' => $user?->getPhotoUrl(),
];
}
public function create(array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$validatedData['created_by'] = auth()->id();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
public function update(string $uuid, array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$report = $this->automatedReportsRepository->findByUuid($uuid);
if (! $report) {
throw new InvalidArgumentException('Report not found');
}
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
/**
* Create an Ask Jiminny report.
*/
public function createAskJiminnyReport(array $data, User $creator): array
{
$validatedData = $this->validateAskJiminnyReportData($data, $creator);
$validatedData['created_by'] = $creator->getId();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
return $this->transformReportFullView($automatedReport);
}
/**
* Update an Ask Jiminny report.
*/
public function updateAskJiminnyReport(AutomatedReport $report, array $data, User $user): array
{
if (! $report->isAskJiminnyReport()) {
throw new InvalidArgumentException('Report is not an Ask Jiminny report');
}
$validatedData = $this->validateAskJiminnyReportData($data, $user);
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
return $this->transformReportFullView($automatedReport);
}
public function updateAskJiminnyReportStatus(AutomatedReport $report, bool $status): array
{
$this->automatedReportsRepository->update($report, ['status' => $status]);
return $this->transformReportFullView($report->fresh());
}
/**
* Validate and transform data for Ask Jiminny reports.
*/
private function validateAskJiminnyReportData(array $data, User $user): array
{
// Validate name
$name = trim($data['report_name'] ?? '');
if (empty($name)) {
throw new InvalidArgumentException('Report name is required');
}
if (mb_strlen($name) > 50) {
throw new InvalidArgumentException('Report name must be 50 characters or less');
}
// Validate frequency (only daily, weekly, monthly for Ask Jiminny)
$frequency = $data['frequency'] ?? null;
$askJiminnyFrequencies = [self::FREQUENCY_DAILY, self::FREQUENCY_WEEKLY, self::FREQUENCY_MONTHLY];
if (! in_array($frequency, $askJiminnyFrequencies, true)) {
throw new InvalidArgumentException('Frequency must be daily, weekly, or monthly');
}
// Validate expiration date
$expiresAt = $data['expires_on'] ?? null;
if (empty($expiresAt)) {
throw new InvalidArgumentException('Expiration date is required');
}
try {
$expiresAtDate = Carbon::parse($expiresAt);
} catch (InvalidFormatException $e) {
throw new InvalidArgumentException('Expiration date format is invalid');
}
$maxExpiration = Carbon::now()->addYear()->endOfDay();
if ($expiresAtDate->gt($maxExpiration)) {
throw new InvalidArgumentException('Expiration date cannot be more than 1 year from now');
}
if ($expiresAtDate->isPast()) {
throw new InvalidArgumentException('Expiration date cannot be in the past');
}
// Validate saved search
$activitySearchId = $data['saved_search'] ?? null;
if (empty($activitySearchId)) {
throw new InvalidArgumentException('Saved search is required');
}
$savedSearch = $this->activitySearchRepository->findByUuidAndUser($activitySearchId, $user);
if (! $savedSearch) {
throw new InvalidArgumentException('Saved search not found or does not belong to you');
}
// Validate saved prompt
$askAnythingPromptId = $data['ask_jiminny_prompt'] ?? null;
if (empty($askAnythingPromptId)) {
throw new InvalidArgumentException('Ask Jiminny prompt is required');
}
$prompt = $this->askAnythingRepository->getPromptByUuid($askAnythingPromptId);
if (! $prompt) {
throw new InvalidArgumentException('Ask Jiminny prompt not found');
}
// Validate status
$status = $data['enabled'] ?? false;
$recipientUserIds = [$user->getId()];
if (! empty($data['share_users'])) {
$sharedUserIds = $this->validateAndGetUserIdsByTeam(
$user->team,
(array) $data['share_users']
);
$recipientUserIds = array_merge($recipientUserIds, $sharedUserIds);
}
$sharedGroupIds = [];
if (! empty($data['share_teams'])) {
$sharedGroupIds = $this->validateAndGetGroupIds($user->team, (array) $data['share_teams']);
}
$recipientUserIds = array_values(array_unique($recipientUserIds));
return [
'team_id' => $user->getTeamId(),
'type' => self::TYPE_ASK_JIMINNY,
'status' => (bool) $status,
'frequency' => $frequency,
'custom_name' => $name,
'activity_search_id' => $savedSearch->getId(),
'ask_anything_prompt_id' => $prompt->getId(),
'expires_at' => $expiresAtDate->toDateString(),
'media_types' => [self::MEDIA_TYPE_PDF],
'call_types' => [],
'recipients' => ['users' => $recipientUserIds],
'groups' => $sharedGroupIds,
];
}
public static function getAskJiminnyFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::ASK_JIMINNY_FREQUENCIES);
}
public function getAskJiminnyReportFilters(User $user): array
{
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
return [
[
'id' => 'prompt',
'label' => 'Prompt',
'options' => $prompts,
],
[
'id' => 'saved_search',
'label' => 'Saved Search',
'options' => $savedSearches,
],
];
}
public function getAskJiminnyReportFormData(User $user, ?AutomatedReport $report = null): array
{
$team = $user->getTeam();
$userTimezone = $user->getTimezone();
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
$teamGroups = $this->groupRepository->getAllByTeam($team)->map(fn ($group) => [
'id' => $group->getUuid(),
'name' => $group->getName(),
])->values()->all();
$shareUsers = $this->recipientsService->getRecipientsFieldData(team: $team)['options'] ?? [];
$sharedTeamsValue = [];
$sharedUsersValue = [];
if ($report) {
$sharedTeamsValue = $this->transformGroups($team, $report->getGroups());
$recipientUserIds = $report->getRecipients()['users'] ?? [];
$creatorId = $report->getAttribute('created_by');
$sharedUserIds = array_values(array_filter(
$recipientUserIds,
static fn ($id) => $id !== $creatorId
));
$sharedUsersValue = collect($sharedUserIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (User $u) => [
'id' => $u->getUuid(),
'name' => $u->getName(),
])
->values()
->all();
}
return [
'fields' => [
[
'id' => 'enabled',
'inputType' => InputTypeEnum::TOGGLE,
'label' => '',
'value' => $report?->getStatus() ?? false,
],
[
'id' => 'report_name',
'inputType' => InputTypeEnum::TEXT,
'label' => 'Name',
'placeholder' => 'Enter name',
'required' => true,
'validation' => ['maxLength' => 50],
'value' => $report?->getCustomName() ?? '',
],
[
'id' => 'frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'label' => 'Frequency',
'required' => true,
'placeholder' => 'Select',
'options' => self::ASK_JIMINNY_FREQUENCIES,
'value' => $report ? $this->transformFrequency($report->getFrequency()) : null,
],
[
'id' => 'expires_on',
'inputType' =>...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71573
|
|
71574
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
102
3
34
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\ActivitySearch\FilterDefinition\InputTypeEnum;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Throwable;
class AutomatedReportsService
{
public const string TYPE_LOSS_ANALYSIS = 'loss_analysis';
public const string TYPE_ASK_JIMINNY = 'ask_jiminny';
/**
* Standard report types (used by kiosk for existing automated reports).
*/
// @TODO this will add filter, however if we need to control feature by FF we need conditional logic
public const array TYPES = [
['id' => 'exec_summary', 'name' => 'Exec Summary'],
['id' => 'coaching_profiles', 'name' => 'Coaching Profiles'],
['id' => 'product_feedback', 'name' => 'Product Feedback'],
['id' => self::TYPE_LOSS_ANALYSIS, 'name' => 'Loss Analysis'],
// ['id' => 'questions', 'name' => 'Questions'],
// ['id' => 'statistical_quant', 'name' => 'Statistical Quantitative'],
];
public const array ALL_TYPES = [
...self::TYPES,
['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'],
];
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
/**
* Frequencies for standard (non-Ask Jiminny) reports.
*/
public const array FREQUENCIES = [
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
/**
* Frequencies for Ask Jiminny reports.
*/
public const array ASK_JIMINNY_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
];
public const string MEDIA_TYPE_PDF = 'pdf';
public const string MEDIA_TYPE_PODCAST = 'podcast';
public const array MEDIA_TYPES = [self::MEDIA_TYPE_PDF, self::MEDIA_TYPE_PODCAST];
public const array MEDIA_TYPE_OBJECT_PDF = ['id' => self::MEDIA_TYPE_PDF, 'name' => 'PDF'];
public const array MEDIA_TYPE_OBJECT_PODCAST = ['id' => self::MEDIA_TYPE_PODCAST, 'name' => 'Podcast'];
public const array MEDIA_TYPE_OBJECTS = [self::MEDIA_TYPE_OBJECT_PDF, self::MEDIA_TYPE_OBJECT_PODCAST];
public const array CALL_TYPE_CONFERENCE = ['id' => 'conference', 'name' => 'Conference'];
public const array CALL_TYPE_DIALER = ['id' => 'dialer', 'name' => 'Dialer'];
public const int SENT_REPORT_AT_HOURS = 5;
public const string PDF_KEY = 'pdf';
public const string AUDIO_KEY = 'audio';
private const array ALL_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
private const string S3_DIR = 'reports';
private const array FILE_EXTENSIONS_VARIANTS = ['html', 'MD', 'pdf'];
private const array FILE_PODCAST_EXTENSIONS_VARIANTS = ['json', 'mp3', 'ssml'];
public function __construct(
private readonly TeamRepository $teamRepository,
private readonly GroupRepository $groupRepository,
private readonly UserRepository $userRepository,
private readonly StageRepository $stageRepository,
private readonly DealStagesService $dealStagesService,
private readonly RecipientsService $recipientsService,
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly Webhook $webhookService,
private readonly BusDispatcher $dispatcher,
private readonly ActivityTypeService $activityTypeService,
private readonly PlaybookCategoryRepository $playbookCategoryRepository,
private readonly AskAnythingPromptService $askAnythingPromptService,
private readonly SearchRepository $activitySearchRepository,
private readonly AskAnythingRepository $askAnythingRepository,
) {
}
public static function getTypes(): array
{
$types = self::TYPES;
return array_map(static function ($type) {
return $type['id'];
}, $types);
}
public static function getCallTypes(): array
{
return array_map(static function ($callType) {
return $callType['id'];
}, [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER]);
}
public static function getFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::FREQUENCIES);
}
// front-facing structure
public function getReportEnabledFieldData(bool $value = false): array
{
return [
'id' => 'report_enabled',
'label' => '',
'inputType' => InputTypeEnum::TOGGLE,
'value' => $value,
];
}
// Organizations = Teams
public function getOrganizationFieldData(?string $value = null, bool $shortVersion = false): array
{
$options = $this->getTeams();
if ($shortVersion) {
return [
'id' => 'organization',
'label' => 'Organization',
'options' => $options,
];
}
return [
'id' => 'organization',
'label' => 'Organization',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $options,
'value' => $value,
'dependencies' => [
'teams',
'deal_stage_at_call',
'current_deal_stage',
'recipients',
ActivityTypeService::PLAYBOOK_CATEGORIES_KEY,
],
'dependsOn' => [],
];
}
// Teams = Groups
public function getTeamFieldData(array $options = [], array $value = [], bool $shortVersion = false): array
{
if ($shortVersion) {
return [
'id' => 'teams',
'label' => 'Team',
'options' => $options,
];
}
return [
'id' => 'teams',
'label' => 'Team',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => false,
'placeholder' => 'Select',
'options' => $options,
'value' => $value, // value should be an array of objects {id, name}
'dependencies' => [ActivityTypeService::PLAYBOOK_CATEGORIES_KEY],
'dependsOn' => [],
];
}
public function getReportTypeFieldData(?string $value = null, bool $shortVersion = false, ?Team $team = null): array
{
$types = [];
if ($team instanceof Team) {
if ($team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
$types = self::TYPES;
}
if ($team->hasFeature(FeatureEnum::ASK_JIMINNY_REPORTS)) {
$types[] = ['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'];
}
} else {
$types = self::TYPES;
}
if ($shortVersion) {
return [
'id' => 'report_type',
'label' => 'Report Type',
'options' => $types,
];
}
return [
'id' => 'report_type',
'label' => 'Report Type',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $types,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getFrequencyFieldData(?string $value = null): array
{
return [
'id' => 'frequency',
'label' => 'Frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => self::FREQUENCIES,
'value' => $value,
'dependencies' => ['period'],
'dependsOn' => [],
];
}
public function getPeriodFieldData(?string $valueStartDate = null, ?string $valueEndDate = null): array
{
return [
'id' => 'period',
'label' => 'Select one-off period',
'inputType' => InputTypeEnum::DATE_RANGE,
'required' => true,
'placeholder' => 'Select',
'value' => ['startDate' => $valueStartDate, 'endDate' => $valueEndDate],
'queryParams' => [
'startDate' => 'start_date_period',
'endDate' => 'end_date_period',
],
'dependencies' => [],
'dependsOn' => ['frequency'],
];
}
public function getActivityTypesFieldData(?Team $team = null, array $value = [], array $teamsFilter = []): array
{
return $this->activityTypeService->getActivityTypeFieldData(team: $team, value: $value, groupIds: $teamsFilter);
}
public function getDealStageAtCallFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getDealStageAtCallFieldData(team: $team, value: $value);
}
public function getCurrentDealStageFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getCurrentDealStageFieldData(team: $team, value: $value);
}
public function getDealValueFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'deal_value',
'label' => 'Deal Value',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_deal_value',
'max' => 'max_deal_value',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallTypeFieldData(bool $conferenceOn = false, bool $dialerOn = false): array
{
$value = [];
$conferenceOn && $value[] = self::CALL_TYPE_CONFERENCE;
$dialerOn && $value[] = self::CALL_TYPE_DIALER;
return [
'id' => 'call_type',
'label' => 'Call Type',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => [
self::CALL_TYPE_CONFERENCE,
self::CALL_TYPE_DIALER,
],
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getMediaTypeFieldData(?AutomatedReport $report = null): array
{
$value = [];
if ($report) {
$value = $this->transformMediaTypes($report);
}
return [
'id' => 'media_types',
'label' => 'Export as',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => self::MEDIA_TYPE_OBJECTS,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallDurationFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'call_duration',
'label' => 'Call Duration',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_call_duration',
'max' => 'max_call_duration',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getRecipientsFieldData(?Team $team = null, array $value = []): array
{
return $this->recipientsService->getRecipientsFieldData(team: $team, value: $value);
}
public function getJiminnyRecipientsFieldData(array $value = []): array
{
return $this->recipientsService->getJiminnyRecipientsFieldData($value);
}
public function getAdditionalPromptInputFieldData(?string $value = null): array
{
return [
'id' => 'additional_prompt_input',
'label' => 'Special requirements',
'inputType' => InputTypeEnum::TEXTAREA,
'required' => false,
'placeholder' => 'What should be the focus of the report?',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCustomReportNameFieldData(?string $value = null): array
{
return [
'id' => 'custom_name',
'label' => 'Custom report name',
'inputType' => InputTypeEnum::TEXT,
'required' => false,
'placeholder' => 'Enter custom name',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
// data providers
public function getTeams(): array
{
$teams = $this->teamRepository->getTeamsForKiosk(status: Team::STATUS_ACTIVE);
$teamData = [];
foreach ($teams as $team) {
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
continue;
}
$teamData[] = $this->transformTeam($team);
}
return $teamData;
}
public function getTeamGroups(string $teamUuid): array
{
$data = [];
$team = $this->getTeam($teamUuid);
if ($team !== null) {
$groups = $team->groups()->get();
foreach ($groups as $group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
];
}
}
return $data;
}
public function getTeamsGroupsOptions(array $filterTeamUuids = []): array
{
$data = [];
$teams = $this->getTeams();
foreach ($teams as $team) {
if (! empty($filterTeamUuids) && ! in_array($team['id'], $filterTeamUuids, true)) {
continue;
}
$data[] = [
'label' => $team['name'],
'groups' => $this->getTeamGroups($team['id']),
];
}
return $data;
}
public function getTeam(string $teamUuid): ?Team
{
return $this->teamRepository->idOrUuid($teamUuid);
}
public function getTeamById(int $teamId): ?Team
{
return $this->teamRepository->find($teamId);
}
public function getGroupsUuids(AutomatedReport $report): array
{
$uuids = [];
$reportGroups = $report->getGroups();
foreach ($reportGroups as $groupId) {
if ($group = $this->groupRepository->find($groupId)) {
$uuids[] = $group->getUuid();
}
}
return $uuids;
}
public function getPlaybookCategoriesUuids(AutomatedReport $report): array
{
$uuids = [];
$playbookCategories = $report->getPlaybookCategories();
foreach ($playbookCategories as $id) {
if ($category = $this->playbookCategoryRepository->find($id)) {
$uuids[] = $category->getUuid();
}
}
return $uuids;
}
public function getDealAtCallStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getDealAtCallStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getCurrentDealStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getCurrentDealStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getRecipients());
}
public function getJiminnyUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function extractUserUuids(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => $user->getUuid())
->values()
->all();
}
// get mail data
public function getRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getRecipients());
}
/**
* @return array<UserContract>
*/
public function getRecipientUserObjects(AutomatedReport $report): array
{
$userIds = $report->getRecipients()['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->values()
->all();
}
private function getJiminnyRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function buildRecipientUsers(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => [
'email' => $user->getEmailAddress(),
'name' => $user->getName(),
'timezone' => $user->getTimezone()->getName(),
])
->values()
->all();
}
public function getValidRecipientUsers(AutomatedReport $report, bool $includeJiminny = false): array
{
if ($report->isAskJiminnyReport()) {
$recipients = $this->resolveAskJiminnyRecipients($report);
} else {
$recipients = $this->getRecipientUsers($report);
if ($includeJiminny) {
$recipients = array_merge($recipients, $this->getJiminnyRecipientUsers($report));
}
}
$emails = [];
return array_values(array_filter(
$recipients,
static function ($recipient) use (&$emails) {
if (empty($recipient['email']) || in_array($recipient['email'], $emails, true)) {
return false;
}
$emails[] = $recipient['email'];
return true;
}
));
}
private function resolveAskJiminnyRecipients(AutomatedReport $report): array
{
$recipients = [];
$creator = $report->getCreator();
if ($creator !== null) {
$recipients[] = [
'email' => $creator->getEmailAddress(),
'name' => $creator->getName(),
'timezone' => $creator->getTimezone()->getName(),
];
}
return array_merge(
$recipients,
$this->buildRecipientUsers($report->getRecipients()),
$this->getGroupRecipientUsers($report),
);
}
private function getGroupRecipientUsers(AutomatedReport $report): array
{
$users = [];
foreach ($report->getGroups() as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group === null) {
continue;
}
foreach ($group->getMembers() as $member) {
$users[] = [
'email' => $member->getEmailAddress(),
'name' => $member->getName(),
'timezone' => $member->getTimezone()->getName(),
];
}
}
return $users;
}
public function getReportTypeName(AutomatedReportResult $report): string
{
$type = $report->getReport()->getType();
$getType = $this->transformReportType($type);
return $getType['name'];
}
public function getReportPeriodName(AutomatedReportResult $report): string
{
$from = $report->getFromDate();
$to = $report->getToDate();
$frequency = $report->getReport()->getFrequency();
if ($from === null || $to === null) {
if (! $report->getReport()->isAskJiminnyReport()) {
$invalidPeriod = $from === null ? 'from' : 'to';
throw new ApplicationException('Report period is invalid: ' . $invalidPeriod);
}
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
}
return $this->formatReportPeriodName($frequency, $from, $to);
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function getReportTeamsName(AutomatedReportResult $report): string
{
$groups = $report->getGroups();
if (empty($groups)) {
return 'All';
}
// Get group names from repository
$groupNames = [];
foreach ($groups as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group) {
$groupNames[] = $group->getName();
}
}
if (count($groupNames) === 1) {
// Single team format
$teamsName = $groupNames[0];
} else {
// Multiple teams format
$teamsName = implode(', ', $groupNames);
}
return $teamsName;
}
public function getReportFileName(AutomatedReportResult $report): string
{
$customName = $report->getReport()->getCustomName();
$periodName = $this->getReportPeriodName($report);
$filenameSuffix = $this->getFilenameSuffix($report);
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$customName} - {$periodName}");
}
$baseName = $this->getReportTypeName($report);
if ($filenameSuffix) {
$baseName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$baseName} - {$periodName} - {$this->getReportTeamsName($report)}");
}
public function getReportFileNameWithExtension(AutomatedReportResult $result): string
{
$extension = $this->getMediaTypeMetadata($result)['extension'];
return $this->getReportFileName($result) . '.' . $extension;
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
public function isUserRecipientOfReport(User $user, AutomatedReport $report): bool
{
$recipientIds = array_map('intval', $report->getRecipients()['users'] ?? []);
if (in_array($user->getId(), $recipientIds, true)) {
return true;
}
if ($report->isAskJiminnyReport()) {
$groupId = $user->getGroupId();
if ($groupId !== null && in_array($groupId, $report->getGroups(), true)) {
return true;
}
}
return false;
}
public function transformReportResults(Collection $automatedReportResults): array
{
$data = [];
foreach ($automatedReportResults as $automatedReportResult) {
/** @var AutomatedReportResult $automatedReportResult */
$report = $automatedReportResult->getReport();
$createdBy = $report->getCreator();
$creator = [
'id' => $createdBy?->getUuid(),
'name' => $createdBy?->getName(),
'email' => $createdBy?->getEmailAddress(),
'photoUrl' => $createdBy?->getPhotoUrl(),
];
$data[] = [
'id' => $automatedReportResult->getUuid(),
'name' => $automatedReportResult->getName(),
'frequency' => $this->transformFrequency($report->getFrequency()),
'recipients' => $this->buildRecipients($report),
'report_type' => $this->transformReportType($report->getType()),
'media_type' => $automatedReportResult->getMediaType(),
'downloadUrl' => $this->generateReportResultDownloadUrl($automatedReportResult),
'viewUrl' => $this->generateReportResultViewUrl($automatedReportResult),
'generated_at' => $automatedReportResult->getGeneratedAt()?->toIso8601String(),
'creator' => $creator,
];
}
return $data;
}
private function buildRecipients(AutomatedReport $report): array
{
$creatorUuid = $report->getCreator()?->getUuid();
$recipients = array_values(array_filter(
$this->transformRecipients($report->getRecipients()),
static fn (array $recipient): bool => $recipient['id'] !== $creatorUuid,
));
if (! $report->isAskJiminnyReport()) {
return $recipients;
}
return [
...array_values($this->transformGroups(team: $report->getTeam(), groupsIds: $report->getGroups())),
...$recipients,
];
}
public function hasCallTypeConference(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_CONFERENCE['id'], $report->getCallTypes(), true);
}
public function hasCallTypeDialer(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_DIALER['id'], $report->getCallTypes(), true);
}
// transformers
private function transformTeam(Team $team): array
{
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
return [];
}
return [
'id' => $team->getUuid(),
'name' => $team->getName(),
];
}
private function transformReportFullView(AutomatedReport $report): array
{
$base = $this->transformReportBase($report);
return $report->getType() === self::TYPE_ASK_JIMINNY
? $base + $this->transformAskJiminnyFields($report)
: $base + $this->transformStandardReportFields($report);
}
private function transformReportBase(AutomatedReport $report): array
{
return [
'id' => $report->getUuid(),
'organization' => $this->transformOrganization(team: $report->getTeam()),
'report_type' => $this->transformReportType($report->getType()),
'frequency' => $this->transformFrequency($report->getFrequency()),
];
}
private function transformStandardReportFields(AutomatedReport $report): array
{
$team = $report->getTeam();
return [
'report_enabled' => $report->getStatus(),
'start_date_period' => $report->getFrom()?->format('Y-m-d H:i:s'),
'end_date_period' => $report->getTo()?->format('Y-m-d H:i:s'),
'deal_value_min' => $report->getDealValueMin(),
'deal_value_max' => $report->getDealValueMax(),
'call_types' => $this->transformCallType($report->getCallTypes()),
'media_types' => $this->transformMediaTypes($report),
'call_duration_min' => $this->transformDurationToMinutes($report->getCallDurationMin()),
'call_duration_max' => $this->transformDurationToMinutes($report->getCallDurationMax()),
'teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'deal_at_call_stages' => $this->transformStages(team: $team, stagesIds: $report->getDealAtCallStages()),
'current_deal_stages' => $this->transformStages(team: $team, stagesIds: $report->getCurrentDealStages()),
'recipients' => $this->transformRecipients($report->getRecipients()),
'created_by' => $this->transformCreator($report->getCreator()),
'additional_prompt_input' => $report->getAdditionalPromptInput(),
'custom_name' => $report->getCustomName(),
'created_at' => $report->getCreatedAt()->format('Y-m-d H:i:s'),
'updated_at' => $report->getUpdatedAt()->format('Y-m-d H:i:s'),
'deleted_at' => $report->getDeletedAt()?->format('Y-m-d H:i:s'),
];
}
private function transformAskJiminnyFields(AutomatedReport $report): array
{
$team = $report->getTeam();
$creatorId = $report->getAttribute('created_by');
$explicitUserIds = array_values(array_filter(
$report->getRecipients()['users'] ?? [],
static fn ($id) => $id !== $creatorId
));
return [
'report_name' => $report->getCustomName(),
'enabled' => $report->getStatus(),
'share_teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'share_users' => $this->transformRecipients(['users' => $explicitUserIds]),
'saved_search' => $this->transformSafeSearch($report->getSavedSearch()),
'ask_jiminny_prompt' => $this->transformAskJiminnyPrompt($report->getAskAnythingPrompt()),
'expires_on' => $report->getExpiresAt()?->format('Y-m-d'),
];
}
private function transformOrganization(?Team $team): array
{
return [
'id' => $team?->getUuid(),
'name' => $team?->getName(),
];
}
private function transformReportType(string $type): array
{
foreach (self::ALL_TYPES as $typeItem) {
if ($typeItem['id'] === $type) {
return $typeItem;
}
}
return [];
}
private function transformCallType(array $types): array
{
$result = [];
$callTypes = [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER];
foreach ($types as $type) {
foreach ($callTypes as $callTypeItem) {
if ($callTypeItem['id'] === $type) {
$result[] = $callTypeItem;
break;
}
}
}
return $result;
}
private function transformMediaTypes(AutomatedReport $report): array
{
$values = [];
foreach ($report->getMediaTypes() as $mediaType) {
if (! in_array($mediaType, self::MEDIA_TYPES, true)) {
continue;
}
$values[] = match ($mediaType) {
self::MEDIA_TYPE_PDF => self::MEDIA_TYPE_OBJECT_PDF,
self::MEDIA_TYPE_PODCAST => self::MEDIA_TYPE_OBJECT_PODCAST,
};
}
return $values;
}
private function transformFrequency(string $frequency): array
{
foreach (self::ALL_FREQUENCIES as $frequencyItem) {
if ($frequencyItem['id'] === $frequency) {
return $frequencyItem;
}
}
return [];
}
public function transformDurationToMinutes(?int $duration): ?int
{
if (! $duration) {
return null;
}
return (int) ($duration / 60);
}
private function transformGroups(?Team $team, array $groupsIds): array
{
if (empty($groupsIds) || ! $team) {
return [];
}
$data = [];
foreach ($groupsIds as $groupId) {
$group = $team->groups()->where('id', $groupId)->first();
if ($group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
'photoUrl' => $group->getPhotoUrl(),
];
}
}
return $data;
}
private function transformStages(?Team $team, array $stagesIds): array
{
if (empty($stagesIds) || ! $team) {
return [];
}
$data = [];
foreach ($stagesIds as $stageId) {
$stage = $team->stages()->where('id', $stageId)->first();
if ($stage) {
$data[] = [
'id' => $stage->getUuid(),
'name' => $stage->getName(),
];
}
}
return $data;
}
private function transformRecipients(array $recipients): array
{
$users = [];
foreach ($recipients['users'] ?? [] as $userId) {
$users[] = $this->transformUser($userId);
}
return $users;
}
private function transformCreator(?User $user): ?array
{
if ($user === null) {
return null;
}
return $this->transformUser($user->getId());
}
private function transformAskJiminnyPrompt(?AskAnythingPrompt $prompt): ?array
{
if ($prompt === null) {
return null;
}
return [
'id' => $prompt->getUuid(),
'name' => $prompt->getTitle(),
];
}
private function transformSafeSearch(?Search $search): ?array
{
if ($search === null) {
return null;
}
return [
'id' => $search->getUuid(),
'name' => $search->getName(),
];
}
private function transformUser(int $userId): array
{
/* @var ?User $user */
$user = $this->userRepository->find($userId);
return [
'id' => $user?->getUuid(),
'name' => $user?->getName(),
'email' => $user?->getEmailAddress(),
'photoUrl' => $user?->getPhotoUrl(),
];
}
public function create(array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$validatedData['created_by'] = auth()->id();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
public function update(string $uuid, array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$report = $this->automatedReportsRepository->findByUuid($uuid);
if (! $report) {
throw new InvalidArgumentException('Report not found');
}
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
/**
* Create an Ask Jiminny report.
*/
public function createAskJiminnyReport(array $data, User $creator): array
{
$validatedData = $this->validateAskJiminnyReportData($data, $creator);
$validatedData['created_by'] = $creator->getId();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
return $this->transformReportFullView($automatedReport);
}
/**
* Update an Ask Jiminny report.
*/
public function updateAskJiminnyReport(AutomatedReport $report, array $data, User $user): array
{
if (! $report->isAskJiminnyReport()) {
throw new InvalidArgumentException('Report is not an Ask Jiminny report');
}
$validatedData = $this->validateAskJiminnyReportData($data, $user);
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
return $this->transformReportFullView($automatedReport);
}
public function updateAskJiminnyReportStatus(AutomatedReport $report, bool $status): array
{
$this->automatedReportsRepository->update($report, ['status' => $status]);
return $this->transformReportFullView($report->fresh());
}
/**
* Validate and transform data for Ask Jiminny reports.
*/
private function validateAskJiminnyReportData(array $data, User $user): array
{
// Validate name
$name = trim($data['report_name'] ?? '');
if (empty($name)) {
throw new InvalidArgumentException('Report name is required');
}
if (mb_strlen($name) > 50) {
throw new InvalidArgumentException('Report name must be 50 characters or less');
}
// Validate frequency (only daily, weekly, monthly for Ask Jiminny)
$frequency = $data['frequency'] ?? null;
$askJiminnyFrequencies = [self::FREQUENCY_DAILY, self::FREQUENCY_WEEKLY, self::FREQUENCY_MONTHLY];
if (! in_array($frequency, $askJiminnyFrequencies, true)) {
throw new InvalidArgumentException('Frequency must be daily, weekly, or monthly');
}
// Validate expiration date
$expiresAt = $data['expires_on'] ?? null;
if (empty($expiresAt)) {
throw new InvalidArgumentException('Expiration date is required');
}
try {
$expiresAtDate = Carbon::parse($expiresAt);
} catch (InvalidFormatException $e) {
throw new InvalidArgumentException('Expiration date format is invalid');
}
$maxExpiration = Carbon::now()->addYear()->endOfDay();
if ($expiresAtDate->gt($maxExpiration)) {
throw new InvalidArgumentException('Expiration date cannot be more than 1 year from now');
}
if ($expiresAtDate->isPast()) {
throw new InvalidArgumentException('Expiration date cannot be in the past');
}
// Validate saved search
$activitySearchId = $data['saved_search'] ?? null;
if (empty($activitySearchId)) {
throw new InvalidArgumentException('Saved search is required');
}
$savedSearch = $this->activitySearchRepository->findByUuidAndUser($activitySearchId, $user);
if (! $savedSearch) {
throw new InvalidArgumentException('Saved search not found or does not belong to you');
}
// Validate saved prompt
$askAnythingPromptId = $data['ask_jiminny_prompt'] ?? null;
if (empty($askAnythingPromptId)) {
throw new InvalidArgumentException('Ask Jiminny prompt is required');
}
$prompt = $this->askAnythingRepository->getPromptByUuid($askAnythingPromptId);
if (! $prompt) {
throw new InvalidArgumentException('Ask Jiminny prompt not found');
}
// Validate status
$status = $data['enabled'] ?? false;
$recipientUserIds = [$user->getId()];
if (! empty($data['share_users'])) {
$sharedUserIds = $this->validateAndGetUserIdsByTeam(
$user->team,
(array) $data['share_users']
);
$recipientUserIds = array_merge($recipientUserIds, $sharedUserIds);
}
$sharedGroupIds = [];
if (! empty($data['share_teams'])) {
$sharedGroupIds = $this->validateAndGetGroupIds($user->team, (array) $data['share_teams']);
}
$recipientUserIds = array_values(array_unique($recipientUserIds));
return [
'team_id' => $user->getTeamId(),
'type' => self::TYPE_ASK_JIMINNY,
'status' => (bool) $status,
'frequency' => $frequency,
'custom_name' => $name,
'activity_search_id' => $savedSearch->getId(),
'ask_anything_prompt_id' => $prompt->getId(),
'expires_at' => $expiresAtDate->toDateString(),
'media_types' => [self::MEDIA_TYPE_PDF],
'call_types' => [],
'recipients' => ['users' => $recipientUserIds],
'groups' => $sharedGroupIds,
];
}
public static function getAskJiminnyFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::ASK_JIMINNY_FREQUENCIES);
}
public function getAskJiminnyReportFilters(User $user): array
{
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
return [
[
'id' => 'prompt',
'label' => 'Prompt',
'options' => $prompts,
],
[
'id' => 'saved_search',
'label' => 'Saved Search',
'options' => $savedSearches,
],
];
}
public function getAskJiminnyReportFormData(User $user, ?AutomatedReport $report = null): array
{
$team = $user->getTeam();
$userTimezone = $user->getTimezone();
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
$teamGroups = $this->groupRepository->getAllByTeam($team)->map(fn ($group) => [
'id' => $group->getUuid(),
'name' => $group->getName(),
])->values()->all();
$shareUsers = $this->recipientsService->getRecipientsFieldData(team: $team)['options'] ?? [];
$sharedTeamsValue = [];
$sharedUsersValue = [];
if ($report) {
$sharedTeamsValue = $this->transformGroups($team, $report->getGroups());
$recipientUserIds = $report->getRecipients()['users'] ?? [];
$creatorId = $report->getAttribute('created_by');
$sharedUserIds = array_values(array_filter(
$recipientUserIds,
static fn ($id) => $id !== $creatorId
));
$sharedUsersValue = collect($sharedUserIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (User $u) => [
'id' => $u->getUuid(),
'name' => $u->getName(),
])
->values()
->all();
}
return [
'fields' => [
[
'id' => 'enabled',
'inputType' => InputTypeEnum::TOGGLE,
'label' => '',
'value' => $report?->getStatus() ?? false,
],
[
'id' => 'report_name',
'inputType' => InputTypeEnum::TEXT,
'label' => 'Name',
'placeholder' => 'Enter name',
'required' => true,
'validation' => ['maxLength' => 50],
'value' => $report?->getCustomName() ?? '',
],
[
'id' => 'frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'label' => 'Frequency',
'required' => true,
'placeholder' => 'Select',
'options' => self::ASK_JIMINNY_FREQUENCIES,
'value' => $report ? $this->transformFrequency($report->getFrequency()) : null,
],
[
'id' => 'expires_on',
'inputType' =>...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71574
|
|
71575
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
102
3
34
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\ActivitySearch\FilterDefinition\InputTypeEnum;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Throwable;
class AutomatedReportsService
{
public const string TYPE_LOSS_ANALYSIS = 'loss_analysis';
public const string TYPE_ASK_JIMINNY = 'ask_jiminny';
/**
* Standard report types (used by kiosk for existing automated reports).
*/
// @TODO this will add filter, however if we need to control feature by FF we need conditional logic
public const array TYPES = [
['id' => 'exec_summary', 'name' => 'Exec Summary'],
['id' => 'coaching_profiles', 'name' => 'Coaching Profiles'],
['id' => 'product_feedback', 'name' => 'Product Feedback'],
['id' => self::TYPE_LOSS_ANALYSIS, 'name' => 'Loss Analysis'],
// ['id' => 'questions', 'name' => 'Questions'],
// ['id' => 'statistical_quant', 'name' => 'Statistical Quantitative'],
];
public const array ALL_TYPES = [
...self::TYPES,
['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'],
];
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
/**
* Frequencies for standard (non-Ask Jiminny) reports.
*/
public const array FREQUENCIES = [
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
/**
* Frequencies for Ask Jiminny reports.
*/
public const array ASK_JIMINNY_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
];
public const string MEDIA_TYPE_PDF = 'pdf';
public const string MEDIA_TYPE_PODCAST = 'podcast';
public const array MEDIA_TYPES = [self::MEDIA_TYPE_PDF, self::MEDIA_TYPE_PODCAST];
public const array MEDIA_TYPE_OBJECT_PDF = ['id' => self::MEDIA_TYPE_PDF, 'name' => 'PDF'];
public const array MEDIA_TYPE_OBJECT_PODCAST = ['id' => self::MEDIA_TYPE_PODCAST, 'name' => 'Podcast'];
public const array MEDIA_TYPE_OBJECTS = [self::MEDIA_TYPE_OBJECT_PDF, self::MEDIA_TYPE_OBJECT_PODCAST];
public const array CALL_TYPE_CONFERENCE = ['id' => 'conference', 'name' => 'Conference'];
public const array CALL_TYPE_DIALER = ['id' => 'dialer', 'name' => 'Dialer'];
public const int SENT_REPORT_AT_HOURS = 5;
public const string PDF_KEY = 'pdf';
public const string AUDIO_KEY = 'audio';
private const array ALL_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
private const string S3_DIR = 'reports';
private const array FILE_EXTENSIONS_VARIANTS = ['html', 'MD', 'pdf'];
private const array FILE_PODCAST_EXTENSIONS_VARIANTS = ['json', 'mp3', 'ssml'];
public function __construct(
private readonly TeamRepository $teamRepository,
private readonly GroupRepository $groupRepository,
private readonly UserRepository $userRepository,
private readonly StageRepository $stageRepository,
private readonly DealStagesService $dealStagesService,
private readonly RecipientsService $recipientsService,
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly Webhook $webhookService,
private readonly BusDispatcher $dispatcher,
private readonly ActivityTypeService $activityTypeService,
private readonly PlaybookCategoryRepository $playbookCategoryRepository,
private readonly AskAnythingPromptService $askAnythingPromptService,
private readonly SearchRepository $activitySearchRepository,
private readonly AskAnythingRepository $askAnythingRepository,
) {
}
public static function getTypes(): array
{
$types = self::TYPES;
return array_map(static function ($type) {
return $type['id'];
}, $types);
}
public static function getCallTypes(): array
{
return array_map(static function ($callType) {
return $callType['id'];
}, [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER]);
}
public static function getFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::FREQUENCIES);
}
// front-facing structure
public function getReportEnabledFieldData(bool $value = false): array
{
return [
'id' => 'report_enabled',
'label' => '',
'inputType' => InputTypeEnum::TOGGLE,
'value' => $value,
];
}
// Organizations = Teams
public function getOrganizationFieldData(?string $value = null, bool $shortVersion = false): array
{
$options = $this->getTeams();
if ($shortVersion) {
return [
'id' => 'organization',
'label' => 'Organization',
'options' => $options,
];
}
return [
'id' => 'organization',
'label' => 'Organization',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $options,
'value' => $value,
'dependencies' => [
'teams',
'deal_stage_at_call',
'current_deal_stage',
'recipients',
ActivityTypeService::PLAYBOOK_CATEGORIES_KEY,
],
'dependsOn' => [],
];
}
// Teams = Groups
public function getTeamFieldData(array $options = [], array $value = [], bool $shortVersion = false): array
{
if ($shortVersion) {
return [
'id' => 'teams',
'label' => 'Team',
'options' => $options,
];
}
return [
'id' => 'teams',
'label' => 'Team',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => false,
'placeholder' => 'Select',
'options' => $options,
'value' => $value, // value should be an array of objects {id, name}
'dependencies' => [ActivityTypeService::PLAYBOOK_CATEGORIES_KEY],
'dependsOn' => [],
];
}
public function getReportTypeFieldData(?string $value = null, bool $shortVersion = false, ?Team $team = null): array
{
$types = [];
if ($team instanceof Team) {
if ($team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
$types = self::TYPES;
}
if ($team->hasFeature(FeatureEnum::ASK_JIMINNY_REPORTS)) {
$types[] = ['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'];
}
} else {
$types = self::TYPES;
}
if ($shortVersion) {
return [
'id' => 'report_type',
'label' => 'Report Type',
'options' => $types,
];
}
return [
'id' => 'report_type',
'label' => 'Report Type',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $types,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getFrequencyFieldData(?string $value = null): array
{
return [
'id' => 'frequency',
'label' => 'Frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => self::FREQUENCIES,
'value' => $value,
'dependencies' => ['period'],
'dependsOn' => [],
];
}
public function getPeriodFieldData(?string $valueStartDate = null, ?string $valueEndDate = null): array
{
return [
'id' => 'period',
'label' => 'Select one-off period',
'inputType' => InputTypeEnum::DATE_RANGE,
'required' => true,
'placeholder' => 'Select',
'value' => ['startDate' => $valueStartDate, 'endDate' => $valueEndDate],
'queryParams' => [
'startDate' => 'start_date_period',
'endDate' => 'end_date_period',
],
'dependencies' => [],
'dependsOn' => ['frequency'],
];
}
public function getActivityTypesFieldData(?Team $team = null, array $value = [], array $teamsFilter = []): array
{
return $this->activityTypeService->getActivityTypeFieldData(team: $team, value: $value, groupIds: $teamsFilter);
}
public function getDealStageAtCallFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getDealStageAtCallFieldData(team: $team, value: $value);
}
public function getCurrentDealStageFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getCurrentDealStageFieldData(team: $team, value: $value);
}
public function getDealValueFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'deal_value',
'label' => 'Deal Value',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_deal_value',
'max' => 'max_deal_value',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallTypeFieldData(bool $conferenceOn = false, bool $dialerOn = false): array
{
$value = [];
$conferenceOn && $value[] = self::CALL_TYPE_CONFERENCE;
$dialerOn && $value[] = self::CALL_TYPE_DIALER;
return [
'id' => 'call_type',
'label' => 'Call Type',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => [
self::CALL_TYPE_CONFERENCE,
self::CALL_TYPE_DIALER,
],
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getMediaTypeFieldData(?AutomatedReport $report = null): array
{
$value = [];
if ($report) {
$value = $this->transformMediaTypes($report);
}
return [
'id' => 'media_types',
'label' => 'Export as',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => self::MEDIA_TYPE_OBJECTS,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallDurationFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'call_duration',
'label' => 'Call Duration',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_call_duration',
'max' => 'max_call_duration',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getRecipientsFieldData(?Team $team = null, array $value = []): array
{
return $this->recipientsService->getRecipientsFieldData(team: $team, value: $value);
}
public function getJiminnyRecipientsFieldData(array $value = []): array
{
return $this->recipientsService->getJiminnyRecipientsFieldData($value);
}
public function getAdditionalPromptInputFieldData(?string $value = null): array
{
return [
'id' => 'additional_prompt_input',
'label' => 'Special requirements',
'inputType' => InputTypeEnum::TEXTAREA,
'required' => false,
'placeholder' => 'What should be the focus of the report?',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCustomReportNameFieldData(?string $value = null): array
{
return [
'id' => 'custom_name',
'label' => 'Custom report name',
'inputType' => InputTypeEnum::TEXT,
'required' => false,
'placeholder' => 'Enter custom name',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
// data providers
public function getTeams(): array
{
$teams = $this->teamRepository->getTeamsForKiosk(status: Team::STATUS_ACTIVE);
$teamData = [];
foreach ($teams as $team) {
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
continue;
}
$teamData[] = $this->transformTeam($team);
}
return $teamData;
}
public function getTeamGroups(string $teamUuid): array
{
$data = [];
$team = $this->getTeam($teamUuid);
if ($team !== null) {
$groups = $team->groups()->get();
foreach ($groups as $group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
];
}
}
return $data;
}
public function getTeamsGroupsOptions(array $filterTeamUuids = []): array
{
$data = [];
$teams = $this->getTeams();
foreach ($teams as $team) {
if (! empty($filterTeamUuids) && ! in_array($team['id'], $filterTeamUuids, true)) {
continue;
}
$data[] = [
'label' => $team['name'],
'groups' => $this->getTeamGroups($team['id']),
];
}
return $data;
}
public function getTeam(string $teamUuid): ?Team
{
return $this->teamRepository->idOrUuid($teamUuid);
}
public function getTeamById(int $teamId): ?Team
{
return $this->teamRepository->find($teamId);
}
public function getGroupsUuids(AutomatedReport $report): array
{
$uuids = [];
$reportGroups = $report->getGroups();
foreach ($reportGroups as $groupId) {
if ($group = $this->groupRepository->find($groupId)) {
$uuids[] = $group->getUuid();
}
}
return $uuids;
}
public function getPlaybookCategoriesUuids(AutomatedReport $report): array
{
$uuids = [];
$playbookCategories = $report->getPlaybookCategories();
foreach ($playbookCategories as $id) {
if ($category = $this->playbookCategoryRepository->find($id)) {
$uuids[] = $category->getUuid();
}
}
return $uuids;
}
public function getDealAtCallStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getDealAtCallStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getCurrentDealStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getCurrentDealStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getRecipients());
}
public function getJiminnyUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function extractUserUuids(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => $user->getUuid())
->values()
->all();
}
// get mail data
public function getRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getRecipients());
}
/**
* @return array<UserContract>
*/
public function getRecipientUserObjects(AutomatedReport $report): array
{
$userIds = $report->getRecipients()['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->values()
->all();
}
private function getJiminnyRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function buildRecipientUsers(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => [
'email' => $user->getEmailAddress(),
'name' => $user->getName(),
'timezone' => $user->getTimezone()->getName(),
])
->values()
->all();
}
public function getValidRecipientUsers(AutomatedReport $report, bool $includeJiminny = false): array
{
if ($report->isAskJiminnyReport()) {
$recipients = $this->resolveAskJiminnyRecipients($report);
} else {
$recipients = $this->getRecipientUsers($report);
if ($includeJiminny) {
$recipients = array_merge($recipients, $this->getJiminnyRecipientUsers($report));
}
}
$emails = [];
return array_values(array_filter(
$recipients,
static function ($recipient) use (&$emails) {
if (empty($recipient['email']) || in_array($recipient['email'], $emails, true)) {
return false;
}
$emails[] = $recipient['email'];
return true;
}
));
}
private function resolveAskJiminnyRecipients(AutomatedReport $report): array
{
$recipients = [];
$creator = $report->getCreator();
if ($creator !== null) {
$recipients[] = [
'email' => $creator->getEmailAddress(),
'name' => $creator->getName(),
'timezone' => $creator->getTimezone()->getName(),
];
}
return array_merge(
$recipients,
$this->buildRecipientUsers($report->getRecipients()),
$this->getGroupRecipientUsers($report),
);
}
private function getGroupRecipientUsers(AutomatedReport $report): array
{
$users = [];
foreach ($report->getGroups() as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group === null) {
continue;
}
foreach ($group->getMembers() as $member) {
$users[] = [
'email' => $member->getEmailAddress(),
'name' => $member->getName(),
'timezone' => $member->getTimezone()->getName(),
];
}
}
return $users;
}
public function getReportTypeName(AutomatedReportResult $report): string
{
$type = $report->getReport()->getType();
$getType = $this->transformReportType($type);
return $getType['name'];
}
public function getReportPeriodName(AutomatedReportResult $report): string
{
$from = $report->getFromDate();
$to = $report->getToDate();
$frequency = $report->getReport()->getFrequency();
if ($from === null || $to === null) {
if (! $report->getReport()->isAskJiminnyReport()) {
$invalidPeriod = $from === null ? 'from' : 'to';
throw new ApplicationException('Report period is invalid: ' . $invalidPeriod);
}
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
}
return $this->formatReportPeriodName($frequency, $from, $to);
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function getReportTeamsName(AutomatedReportResult $report): string
{
$groups = $report->getGroups();
if (empty($groups)) {
return 'All';
}
// Get group names from repository
$groupNames = [];
foreach ($groups as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group) {
$groupNames[] = $group->getName();
}
}
if (count($groupNames) === 1) {
// Single team format
$teamsName = $groupNames[0];
} else {
// Multiple teams format
$teamsName = implode(', ', $groupNames);
}
return $teamsName;
}
public function getReportFileName(AutomatedReportResult $report): string
{
$customName = $report->getReport()->getCustomName();
$periodName = $this->getReportPeriodName($report);
$filenameSuffix = $this->getFilenameSuffix($report);
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$customName} - {$periodName}");
}
$baseName = $this->getReportTypeName($report);
if ($filenameSuffix) {
$baseName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$baseName} - {$periodName} - {$this->getReportTeamsName($report)}");
}
public function getReportFileNameWithExtension(AutomatedReportResult $result): string
{
$extension = $this->getMediaTypeMetadata($result)['extension'];
return $this->getReportFileName($result) . '.' . $extension;
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
public function isUserRecipientOfReport(User $user, AutomatedReport $report): bool
{
$recipientIds = array_map('intval', $report->getRecipients()['users'] ?? []);
if (in_array($user->getId(), $recipientIds, true)) {
return true;
}
if ($report->isAskJiminnyReport()) {
$groupId = $user->getGroupId();
if ($groupId !== null && in_array($groupId, $report->getGroups(), true)) {
return true;
}
}
return false;
}
public function transformReportResults(Collection $automatedReportResults): array
{
$data = [];
foreach ($automatedReportResults as $automatedReportResult) {
/** @var AutomatedReportResult $automatedReportResult */
$report = $automatedReportResult->getReport();
$createdBy = $report->getCreator();
$creator = [
'id' => $createdBy?->getUuid(),
'name' => $createdBy?->getName(),
'email' => $createdBy?->getEmailAddress(),
'photoUrl' => $createdBy?->getPhotoUrl(),
];
$data[] = [
'id' => $automatedReportResult->getUuid(),
'name' => $automatedReportResult->getName(),
'frequency' => $this->transformFrequency($report->getFrequency()),
'recipients' => $this->buildRecipients($report),
'report_type' => $this->transformReportType($report->getType()),
'media_type' => $automatedReportResult->getMediaType(),
'downloadUrl' => $this->generateReportResultDownloadUrl($automatedReportResult),
'viewUrl' => $this->generateReportResultViewUrl($automatedReportResult),
'generated_at' => $automatedReportResult->getGeneratedAt()?->toIso8601String(),
'creator' => $creator,
];
}
return $data;
}
private function buildRecipients(AutomatedReport $report): array
{
$creatorUuid = $report->getCreator()?->getUuid();
$recipients = array_values(array_filter(
$this->transformRecipients($report->getRecipients()),
static fn (array $recipient): bool => $recipient['id'] !== $creatorUuid,
));
if (! $report->isAskJiminnyReport()) {
return $recipients;
}
return [
...array_values($this->transformGroups(team: $report->getTeam(), groupsIds: $report->getGroups())),
...$recipients,
];
}
public function hasCallTypeConference(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_CONFERENCE['id'], $report->getCallTypes(), true);
}
public function hasCallTypeDialer(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_DIALER['id'], $report->getCallTypes(), true);
}
// transformers
private function transformTeam(Team $team): array
{
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
return [];
}
return [
'id' => $team->getUuid(),
'name' => $team->getName(),
];
}
private function transformReportFullView(AutomatedReport $report): array
{
$base = $this->transformReportBase($report);
return $report->getType() === self::TYPE_ASK_JIMINNY
? $base + $this->transformAskJiminnyFields($report)
: $base + $this->transformStandardReportFields($report);
}
private function transformReportBase(AutomatedReport $report): array
{
return [
'id' => $report->getUuid(),
'organization' => $this->transformOrganization(team: $report->getTeam()),
'report_type' => $this->transformReportType($report->getType()),
'frequency' => $this->transformFrequency($report->getFrequency()),
];
}
private function transformStandardReportFields(AutomatedReport $report): array
{
$team = $report->getTeam();
return [
'report_enabled' => $report->getStatus(),
'start_date_period' => $report->getFrom()?->format('Y-m-d H:i:s'),
'end_date_period' => $report->getTo()?->format('Y-m-d H:i:s'),
'deal_value_min' => $report->getDealValueMin(),
'deal_value_max' => $report->getDealValueMax(),
'call_types' => $this->transformCallType($report->getCallTypes()),
'media_types' => $this->transformMediaTypes($report),
'call_duration_min' => $this->transformDurationToMinutes($report->getCallDurationMin()),
'call_duration_max' => $this->transformDurationToMinutes($report->getCallDurationMax()),
'teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'deal_at_call_stages' => $this->transformStages(team: $team, stagesIds: $report->getDealAtCallStages()),
'current_deal_stages' => $this->transformStages(team: $team, stagesIds: $report->getCurrentDealStages()),
'recipients' => $this->transformRecipients($report->getRecipients()),
'created_by' => $this->transformCreator($report->getCreator()),
'additional_prompt_input' => $report->getAdditionalPromptInput(),
'custom_name' => $report->getCustomName(),
'created_at' => $report->getCreatedAt()->format('Y-m-d H:i:s'),
'updated_at' => $report->getUpdatedAt()->format('Y-m-d H:i:s'),
'deleted_at' => $report->getDeletedAt()?->format('Y-m-d H:i:s'),
];
}
private function transformAskJiminnyFields(AutomatedReport $report): array
{
$team = $report->getTeam();
$creatorId = $report->getAttribute('created_by');
$explicitUserIds = array_values(array_filter(
$report->getRecipients()['users'] ?? [],
static fn ($id) => $id !== $creatorId
));
return [
'report_name' => $report->getCustomName(),
'enabled' => $report->getStatus(),
'share_teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'share_users' => $this->transformRecipients(['users' => $explicitUserIds]),
'saved_search' => $this->transformSafeSearch($report->getSavedSearch()),
'ask_jiminny_prompt' => $this->transformAskJiminnyPrompt($report->getAskAnythingPrompt()),
'expires_on' => $report->getExpiresAt()?->format('Y-m-d'),
];
}
private function transformOrganization(?Team $team): array
{
return [
'id' => $team?->getUuid(),
'name' => $team?->getName(),
];
}
private function transformReportType(string $type): array
{
foreach (self::ALL_TYPES as $typeItem) {
if ($typeItem['id'] === $type) {
return $typeItem;
}
}
return [];
}
private function transformCallType(array $types): array
{
$result = [];
$callTypes = [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER];
foreach ($types as $type) {
foreach ($callTypes as $callTypeItem) {
if ($callTypeItem['id'] === $type) {
$result[] = $callTypeItem;
break;
}
}
}
return $result;
}
private function transformMediaTypes(AutomatedReport $report): array
{
$values = [];
foreach ($report->getMediaTypes() as $mediaType) {
if (! in_array($mediaType, self::MEDIA_TYPES, true)) {
continue;
}
$values[] = match ($mediaType) {
self::MEDIA_TYPE_PDF => self::MEDIA_TYPE_OBJECT_PDF,
self::MEDIA_TYPE_PODCAST => self::MEDIA_TYPE_OBJECT_PODCAST,
};
}
return $values;
}
private function transformFrequency(string $frequency): array
{
foreach (self::ALL_FREQUENCIES as $frequencyItem) {
if ($frequencyItem['id'] === $frequency) {
return $frequencyItem;
}
}
return [];
}
public function transformDurationToMinutes(?int $duration): ?int
{
if (! $duration) {
return null;
}
return (int) ($duration / 60);
}
private function transformGroups(?Team $team, array $groupsIds): array
{
if (empty($groupsIds) || ! $team) {
return [];
}
$data = [];
foreach ($groupsIds as $groupId) {
$group = $team->groups()->where('id', $groupId)->first();
if ($group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
'photoUrl' => $group->getPhotoUrl(),
];
}
}
return $data;
}
private function transformStages(?Team $team, array $stagesIds): array
{
if (empty($stagesIds) || ! $team) {
return [];
}
$data = [];
foreach ($stagesIds as $stageId) {
$stage = $team->stages()->where('id', $stageId)->first();
if ($stage) {
$data[] = [
'id' => $stage->getUuid(),
'name' => $stage->getName(),
];
}
}
return $data;
}
private function transformRecipients(array $recipients): array
{
$users = [];
foreach ($recipients['users'] ?? [] as $userId) {
$users[] = $this->transformUser($userId);
}
return $users;
}
private function transformCreator(?User $user): ?array
{
if ($user === null) {
return null;
}
return $this->transformUser($user->getId());
}
private function transformAskJiminnyPrompt(?AskAnythingPrompt $prompt): ?array
{
if ($prompt === null) {
return null;
}
return [
'id' => $prompt->getUuid(),
'name' => $prompt->getTitle(),
];
}
private function transformSafeSearch(?Search $search): ?array
{
if ($search === null) {
return null;
}
return [
'id' => $search->getUuid(),
'name' => $search->getName(),
];
}
private function transformUser(int $userId): array
{
/* @var ?User $user */
$user = $this->userRepository->find($userId);
return [
'id' => $user?->getUuid(),
'name' => $user?->getName(),
'email' => $user?->getEmailAddress(),
'photoUrl' => $user?->getPhotoUrl(),
];
}
public function create(array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$validatedData['created_by'] = auth()->id();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
public function update(string $uuid, array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$report = $this->automatedReportsRepository->findByUuid($uuid);
if (! $report) {
throw new InvalidArgumentException('Report not found');
}
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
/**
* Create an Ask Jiminny report.
*/
public function createAskJiminnyReport(array $data, User $creator): array
{
$validatedData = $this->validateAskJiminnyReportData($data, $creator);
$validatedData['created_by'] = $creator->getId();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
return $this->transformReportFullView($automatedReport);
}
/**
* Update an Ask Jiminny report.
*/
public function updateAskJiminnyReport(AutomatedReport $report, array $data, User $user): array
{
if (! $report->isAskJiminnyReport()) {
throw new InvalidArgumentException('Report is not an Ask Jiminny report');
}
$validatedData = $this->validateAskJiminnyReportData($data, $user);
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
return $this->transformReportFullView($automatedReport);
}
public function updateAskJiminnyReportStatus(AutomatedReport $report, bool $status): array
{
$this->automatedReportsRepository->update($report, ['status' => $status]);
return $this->transformReportFullView($report->fresh());
}
/**
* Validate and transform data for Ask Jiminny reports.
*/
private function validateAskJiminnyReportData(array $data, User $user): array
{
// Validate name
$name = trim($data['report_name'] ?? '');
if (empty($name)) {
throw new InvalidArgumentException('Report name is required');
}
if (mb_strlen($name) > 50) {
throw new InvalidArgumentException('Report name must be 50 characters or less');
}
// Validate frequency (only daily, weekly, monthly for Ask Jiminny)
$frequency = $data['frequency'] ?? null;
$askJiminnyFrequencies = [self::FREQUENCY_DAILY, self::FREQUENCY_WEEKLY, self::FREQUENCY_MONTHLY];
if (! in_array($frequency, $askJiminnyFrequencies, true)) {
throw new InvalidArgumentException('Frequency must be daily, weekly, or monthly');
}
// Validate expiration date
$expiresAt = $data['expires_on'] ?? null;
if (empty($expiresAt)) {
throw new InvalidArgumentException('Expiration date is required');
}
try {
$expiresAtDate = Carbon::parse($expiresAt);
} catch (InvalidFormatException $e) {
throw new InvalidArgumentException('Expiration date format is invalid');
}
$maxExpiration = Carbon::now()->addYear()->endOfDay();
if ($expiresAtDate->gt($maxExpiration)) {
throw new InvalidArgumentException('Expiration date cannot be more than 1 year from now');
}
if ($expiresAtDate->isPast()) {
throw new InvalidArgumentException('Expiration date cannot be in the past');
}
// Validate saved search
$activitySearchId = $data['saved_search'] ?? null;
if (empty($activitySearchId)) {
throw new InvalidArgumentException('Saved search is required');
}
$savedSearch = $this->activitySearchRepository->findByUuidAndUser($activitySearchId, $user);
if (! $savedSearch) {
throw new InvalidArgumentException('Saved search not found or does not belong to you');
}
// Validate saved prompt
$askAnythingPromptId = $data['ask_jiminny_prompt'] ?? null;
if (empty($askAnythingPromptId)) {
throw new InvalidArgumentException('Ask Jiminny prompt is required');
}
$prompt = $this->askAnythingRepository->getPromptByUuid($askAnythingPromptId);
if (! $prompt) {
throw new InvalidArgumentException('Ask Jiminny prompt not found');
}
// Validate status
$status = $data['enabled'] ?? false;
$recipientUserIds = [$user->getId()];
if (! empty($data['share_users'])) {
$sharedUserIds = $this->validateAndGetUserIdsByTeam(
$user->team,
(array) $data['share_users']
);
$recipientUserIds = array_merge($recipientUserIds, $sharedUserIds);
}
$sharedGroupIds = [];
if (! empty($data['share_teams'])) {
$sharedGroupIds = $this->validateAndGetGroupIds($user->team, (array) $data['share_teams']);
}
$recipientUserIds = array_values(array_unique($recipientUserIds));
return [
'team_id' => $user->getTeamId(),
'type' => self::TYPE_ASK_JIMINNY,
'status' => (bool) $status,
'frequency' => $frequency,
'custom_name' => $name,
'activity_search_id' => $savedSearch->getId(),
'ask_anything_prompt_id' => $prompt->getId(),
'expires_at' => $expiresAtDate->toDateString(),
'media_types' => [self::MEDIA_TYPE_PDF],
'call_types' => [],
'recipients' => ['users' => $recipientUserIds],
'groups' => $sharedGroupIds,
];
}
public static function getAskJiminnyFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::ASK_JIMINNY_FREQUENCIES);
}
public function getAskJiminnyReportFilters(User $user): array
{
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
return [
[
'id' => 'prompt',
'label' => 'Prompt',
'options' => $prompts,
],
[
'id' => 'saved_search',
'label' => 'Saved Search',
'options' => $savedSearches,
],
];
}
public function getAskJiminnyReportFormData(User $user, ?AutomatedReport $report = null): array
{
$team = $user->getTeam();
$userTimezone = $user->getTimezone();
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
$teamGroups = $this->groupRepository->getAllByTeam($team)->map(fn ($group) => [
'id' => $group->getUuid(),
'name' => $group->getName(),
])->values()->all();
$shareUsers = $this->recipientsService->getRecipientsFieldData(team: $team)['options'] ?? [];
$sharedTeamsValue = [];
$sharedUsersValue = [];
if ($report) {
$sharedTeamsValue = $this->transformGroups($team, $report->getGroups());
$recipientUserIds = $report->getRecipients()['users'] ?? [];
$creatorId = $report->getAttribute('created_by');
$sharedUserIds = array_values(array_filter(
$recipientUserIds,
static fn ($id) => $id !== $creatorId
));
$sharedUsersValue = collect($sharedUserIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (User $u) => [
'id' => $u->getUuid(),
'name' => $u->getName(),
])
->values()
->all();
}
return [
'fields' => [
[
'id' => 'enabled',
'inputType' => InputTypeEnum::TOGGLE,
'label' => '',
'value' => $report?->getStatus() ?? false,
],
[
'id' => 'report_name',
'inputType' => InputTypeEnum::TEXT,
'label' => 'Name',
'placeholder' => 'Enter name',
'required' => true,
'validation' => ['maxLength' => 50],
'value' => $report?->getCustomName() ?? '',
],
[
'id' => 'frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'label' => 'Frequency',
'required' => true,
'placeholder' => 'Select',
'options' => self::ASK_JIMINNY_FREQUENCIES,
'value' => $report ? $this->transformFrequency($report->getFrequency()) : null,
],
[
'id' => 'expires_on',
'inputType' =>...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71575
|
|
71576
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
102
3
34
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\ActivitySearch\FilterDefinition\InputTypeEnum;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Throwable;
class AutomatedReportsService
{
public const string TYPE_LOSS_ANALYSIS = 'loss_analysis';
public const string TYPE_ASK_JIMINNY = 'ask_jiminny';
/**
* Standard report types (used by kiosk for existing automated reports).
*/
// @TODO this will add filter, however if we need to control feature by FF we need conditional logic
public const array TYPES = [
['id' => 'exec_summary', 'name' => 'Exec Summary'],
['id' => 'coaching_profiles', 'name' => 'Coaching Profiles'],
['id' => 'product_feedback', 'name' => 'Product Feedback'],
['id' => self::TYPE_LOSS_ANALYSIS, 'name' => 'Loss Analysis'],
// ['id' => 'questions', 'name' => 'Questions'],
// ['id' => 'statistical_quant', 'name' => 'Statistical Quantitative'],
];
public const array ALL_TYPES = [
...self::TYPES,
['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'],
];
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
/**
* Frequencies for standard (non-Ask Jiminny) reports.
*/
public const array FREQUENCIES = [
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
/**
* Frequencies for Ask Jiminny reports.
*/
public const array ASK_JIMINNY_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
];
public const string MEDIA_TYPE_PDF = 'pdf';
public const string MEDIA_TYPE_PODCAST = 'podcast';
public const array MEDIA_TYPES = [self::MEDIA_TYPE_PDF, self::MEDIA_TYPE_PODCAST];
public const array MEDIA_TYPE_OBJECT_PDF = ['id' => self::MEDIA_TYPE_PDF, 'name' => 'PDF'];
public const array MEDIA_TYPE_OBJECT_PODCAST = ['id' => self::MEDIA_TYPE_PODCAST, 'name' => 'Podcast'];
public const array MEDIA_TYPE_OBJECTS = [self::MEDIA_TYPE_OBJECT_PDF, self::MEDIA_TYPE_OBJECT_PODCAST];
public const array CALL_TYPE_CONFERENCE = ['id' => 'conference', 'name' => 'Conference'];
public const array CALL_TYPE_DIALER = ['id' => 'dialer', 'name' => 'Dialer'];
public const int SENT_REPORT_AT_HOURS = 5;
public const string PDF_KEY = 'pdf';
public const string AUDIO_KEY = 'audio';
private const array ALL_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
private const string S3_DIR = 'reports';
private const array FILE_EXTENSIONS_VARIANTS = ['html', 'MD', 'pdf'];
private const array FILE_PODCAST_EXTENSIONS_VARIANTS = ['json', 'mp3', 'ssml'];
public function __construct(
private readonly TeamRepository $teamRepository,
private readonly GroupRepository $groupRepository,
private readonly UserRepository $userRepository,
private readonly StageRepository $stageRepository,
private readonly DealStagesService $dealStagesService,
private readonly RecipientsService $recipientsService,
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly Webhook $webhookService,
private readonly BusDispatcher $dispatcher,
private readonly ActivityTypeService $activityTypeService,
private readonly PlaybookCategoryRepository $playbookCategoryRepository,
private readonly AskAnythingPromptService $askAnythingPromptService,
private readonly SearchRepository $activitySearchRepository,
private readonly AskAnythingRepository $askAnythingRepository,
) {
}
public static function getTypes(): array
{
$types = self::TYPES;
return array_map(static function ($type) {
return $type['id'];
}, $types);
}
public static function getCallTypes(): array
{
return array_map(static function ($callType) {
return $callType['id'];
}, [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER]);
}
public static function getFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::FREQUENCIES);
}
// front-facing structure
public function getReportEnabledFieldData(bool $value = false): array
{
return [
'id' => 'report_enabled',
'label' => '',
'inputType' => InputTypeEnum::TOGGLE,
'value' => $value,
];
}
// Organizations = Teams
public function getOrganizationFieldData(?string $value = null, bool $shortVersion = false): array
{
$options = $this->getTeams();
if ($shortVersion) {
return [
'id' => 'organization',
'label' => 'Organization',
'options' => $options,
];
}
return [
'id' => 'organization',
'label' => 'Organization',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $options,
'value' => $value,
'dependencies' => [
'teams',
'deal_stage_at_call',
'current_deal_stage',
'recipients',
ActivityTypeService::PLAYBOOK_CATEGORIES_KEY,
],
'dependsOn' => [],
];
}
// Teams = Groups
public function getTeamFieldData(array $options = [], array $value = [], bool $shortVersion = false): array
{
if ($shortVersion) {
return [
'id' => 'teams',
'label' => 'Team',
'options' => $options,
];
}
return [
'id' => 'teams',
'label' => 'Team',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => false,
'placeholder' => 'Select',
'options' => $options,
'value' => $value, // value should be an array of objects {id, name}
'dependencies' => [ActivityTypeService::PLAYBOOK_CATEGORIES_KEY],
'dependsOn' => [],
];
}
public function getReportTypeFieldData(?string $value = null, bool $shortVersion = false, ?Team $team = null): array
{
$types = [];
if ($team instanceof Team) {
if ($team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
$types = self::TYPES;
}
if ($team->hasFeature(FeatureEnum::ASK_JIMINNY_REPORTS)) {
$types[] = ['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'];
}
} else {
$types = self::TYPES;
}
if ($shortVersion) {
return [
'id' => 'report_type',
'label' => 'Report Type',
'options' => $types,
];
}
return [
'id' => 'report_type',
'label' => 'Report Type',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $types,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getFrequencyFieldData(?string $value = null): array
{
return [
'id' => 'frequency',
'label' => 'Frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => self::FREQUENCIES,
'value' => $value,
'dependencies' => ['period'],
'dependsOn' => [],
];
}
public function getPeriodFieldData(?string $valueStartDate = null, ?string $valueEndDate = null): array
{
return [
'id' => 'period',
'label' => 'Select one-off period',
'inputType' => InputTypeEnum::DATE_RANGE,
'required' => true,
'placeholder' => 'Select',
'value' => ['startDate' => $valueStartDate, 'endDate' => $valueEndDate],
'queryParams' => [
'startDate' => 'start_date_period',
'endDate' => 'end_date_period',
],
'dependencies' => [],
'dependsOn' => ['frequency'],
];
}
public function getActivityTypesFieldData(?Team $team = null, array $value = [], array $teamsFilter = []): array
{
return $this->activityTypeService->getActivityTypeFieldData(team: $team, value: $value, groupIds: $teamsFilter);
}
public function getDealStageAtCallFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getDealStageAtCallFieldData(team: $team, value: $value);
}
public function getCurrentDealStageFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getCurrentDealStageFieldData(team: $team, value: $value);
}
public function getDealValueFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'deal_value',
'label' => 'Deal Value',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_deal_value',
'max' => 'max_deal_value',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallTypeFieldData(bool $conferenceOn = false, bool $dialerOn = false): array
{
$value = [];
$conferenceOn && $value[] = self::CALL_TYPE_CONFERENCE;
$dialerOn && $value[] = self::CALL_TYPE_DIALER;
return [
'id' => 'call_type',
'label' => 'Call Type',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => [
self::CALL_TYPE_CONFERENCE,
self::CALL_TYPE_DIALER,
],
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getMediaTypeFieldData(?AutomatedReport $report = null): array
{
$value = [];
if ($report) {
$value = $this->transformMediaTypes($report);
}
return [
'id' => 'media_types',
'label' => 'Export as',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => self::MEDIA_TYPE_OBJECTS,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallDurationFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'call_duration',
'label' => 'Call Duration',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_call_duration',
'max' => 'max_call_duration',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getRecipientsFieldData(?Team $team = null, array $value = []): array
{
return $this->recipientsService->getRecipientsFieldData(team: $team, value: $value);
}
public function getJiminnyRecipientsFieldData(array $value = []): array
{
return $this->recipientsService->getJiminnyRecipientsFieldData($value);
}
public function getAdditionalPromptInputFieldData(?string $value = null): array
{
return [
'id' => 'additional_prompt_input',
'label' => 'Special requirements',
'inputType' => InputTypeEnum::TEXTAREA,
'required' => false,
'placeholder' => 'What should be the focus of the report?',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCustomReportNameFieldData(?string $value = null): array
{
return [
'id' => 'custom_name',
'label' => 'Custom report name',
'inputType' => InputTypeEnum::TEXT,
'required' => false,
'placeholder' => 'Enter custom name',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
// data providers
public function getTeams(): array
{
$teams = $this->teamRepository->getTeamsForKiosk(status: Team::STATUS_ACTIVE);
$teamData = [];
foreach ($teams as $team) {
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
continue;
}
$teamData[] = $this->transformTeam($team);
}
return $teamData;
}
public function getTeamGroups(string $teamUuid): array
{
$data = [];
$team = $this->getTeam($teamUuid);
if ($team !== null) {
$groups = $team->groups()->get();
foreach ($groups as $group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
];
}
}
return $data;
}
public function getTeamsGroupsOptions(array $filterTeamUuids = []): array
{
$data = [];
$teams = $this->getTeams();
foreach ($teams as $team) {
if (! empty($filterTeamUuids) && ! in_array($team['id'], $filterTeamUuids, true)) {
continue;
}
$data[] = [
'label' => $team['name'],
'groups' => $this->getTeamGroups($team['id']),
];
}
return $data;
}
public function getTeam(string $teamUuid): ?Team
{
return $this->teamRepository->idOrUuid($teamUuid);
}
public function getTeamById(int $teamId): ?Team
{
return $this->teamRepository->find($teamId);
}
public function getGroupsUuids(AutomatedReport $report): array
{
$uuids = [];
$reportGroups = $report->getGroups();
foreach ($reportGroups as $groupId) {
if ($group = $this->groupRepository->find($groupId)) {
$uuids[] = $group->getUuid();
}
}
return $uuids;
}
public function getPlaybookCategoriesUuids(AutomatedReport $report): array
{
$uuids = [];
$playbookCategories = $report->getPlaybookCategories();
foreach ($playbookCategories as $id) {
if ($category = $this->playbookCategoryRepository->find($id)) {
$uuids[] = $category->getUuid();
}
}
return $uuids;
}
public function getDealAtCallStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getDealAtCallStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getCurrentDealStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getCurrentDealStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getRecipients());
}
public function getJiminnyUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function extractUserUuids(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => $user->getUuid())
->values()
->all();
}
// get mail data
public function getRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getRecipients());
}
/**
* @return array<UserContract>
*/
public function getRecipientUserObjects(AutomatedReport $report): array
{
$userIds = $report->getRecipients()['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->values()
->all();
}
private function getJiminnyRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function buildRecipientUsers(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => [
'email' => $user->getEmailAddress(),
'name' => $user->getName(),
'timezone' => $user->getTimezone()->getName(),
])
->values()
->all();
}
public function getValidRecipientUsers(AutomatedReport $report, bool $includeJiminny = false): array
{
if ($report->isAskJiminnyReport()) {
$recipients = $this->resolveAskJiminnyRecipients($report);
} else {
$recipients = $this->getRecipientUsers($report);
if ($includeJiminny) {
$recipients = array_merge($recipients, $this->getJiminnyRecipientUsers($report));
}
}
$emails = [];
return array_values(array_filter(
$recipients,
static function ($recipient) use (&$emails) {
if (empty($recipient['email']) || in_array($recipient['email'], $emails, true)) {
return false;
}
$emails[] = $recipient['email'];
return true;
}
));
}
private function resolveAskJiminnyRecipients(AutomatedReport $report): array
{
$recipients = [];
$creator = $report->getCreator();
if ($creator !== null) {
$recipients[] = [
'email' => $creator->getEmailAddress(),
'name' => $creator->getName(),
'timezone' => $creator->getTimezone()->getName(),
];
}
return array_merge(
$recipients,
$this->buildRecipientUsers($report->getRecipients()),
$this->getGroupRecipientUsers($report),
);
}
private function getGroupRecipientUsers(AutomatedReport $report): array
{
$users = [];
foreach ($report->getGroups() as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group === null) {
continue;
}
foreach ($group->getMembers() as $member) {
$users[] = [
'email' => $member->getEmailAddress(),
'name' => $member->getName(),
'timezone' => $member->getTimezone()->getName(),
];
}
}
return $users;
}
public function getReportTypeName(AutomatedReportResult $report): string
{
$type = $report->getReport()->getType();
$getType = $this->transformReportType($type);
return $getType['name'];
}
public function getReportPeriodName(AutomatedReportResult $report): string
{
$from = $report->getFromDate();
$to = $report->getToDate();
$frequency = $report->getReport()->getFrequency();
if ($from === null || $to === null) {
if (! $report->getReport()->isAskJiminnyReport()) {
$invalidPeriod = $from === null ? 'from' : 'to';
throw new ApplicationException('Report period is invalid: ' . $invalidPeriod);
}
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
}
return $this->formatReportPeriodName($frequency, $from, $to);
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function getReportTeamsName(AutomatedReportResult $report): string
{
$groups = $report->getGroups();
if (empty($groups)) {
return 'All';
}
// Get group names from repository
$groupNames = [];
foreach ($groups as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group) {
$groupNames[] = $group->getName();
}
}
if (count($groupNames) === 1) {
// Single team format
$teamsName = $groupNames[0];
} else {
// Multiple teams format
$teamsName = implode(', ', $groupNames);
}
return $teamsName;
}
public function getReportFileName(AutomatedReportResult $report): string
{
$customName = $report->getReport()->getCustomName();
$periodName = $this->getReportPeriodName($report);
$filenameSuffix = $this->getFilenameSuffix($report);
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$customName} - {$periodName}");
}
$baseName = $this->getReportTypeName($report);
if ($filenameSuffix) {
$baseName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$baseName} - {$periodName} - {$this->getReportTeamsName($report)}");
}
public function getReportFileNameWithExtension(AutomatedReportResult $result): string
{
$extension = $this->getMediaTypeMetadata($result)['extension'];
return $this->getReportFileName($result) . '.' . $extension;
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
public function isUserRecipientOfReport(User $user, AutomatedReport $report): bool
{
$recipientIds = array_map('intval', $report->getRecipients()['users'] ?? []);
if (in_array($user->getId(), $recipientIds, true)) {
return true;
}
if ($report->isAskJiminnyReport()) {
$groupId = $user->getGroupId();
if ($groupId !== null && in_array($groupId, $report->getGroups(), true)) {
return true;
}
}
return false;
}
public function transformReportResults(Collection $automatedReportResults): array
{
$data = [];
foreach ($automatedReportResults as $automatedReportResult) {
/** @var AutomatedReportResult $automatedReportResult */
$report = $automatedReportResult->getReport();
$createdBy = $report->getCreator();
$creator = [
'id' => $createdBy?->getUuid(),
'name' => $createdBy?->getName(),
'email' => $createdBy?->getEmailAddress(),
'photoUrl' => $createdBy?->getPhotoUrl(),
];
$data[] = [
'id' => $automatedReportResult->getUuid(),
'name' => $automatedReportResult->getName(),
'frequency' => $this->transformFrequency($report->getFrequency()),
'recipients' => $this->buildRecipients($report),
'report_type' => $this->transformReportType($report->getType()),
'media_type' => $automatedReportResult->getMediaType(),
'downloadUrl' => $this->generateReportResultDownloadUrl($automatedReportResult),
'viewUrl' => $this->generateReportResultViewUrl($automatedReportResult),
'generated_at' => $automatedReportResult->getGeneratedAt()?->toIso8601String(),
'creator' => $creator,
];
}
return $data;
}
private function buildRecipients(AutomatedReport $report): array
{
$creatorUuid = $report->getCreator()?->getUuid();
$recipients = array_values(array_filter(
$this->transformRecipients($report->getRecipients()),
static fn (array $recipient): bool => $recipient['id'] !== $creatorUuid,
));
if (! $report->isAskJiminnyReport()) {
return $recipients;
}
return [
...array_values($this->transformGroups(team: $report->getTeam(), groupsIds: $report->getGroups())),
...$recipients,
];
}
public function hasCallTypeConference(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_CONFERENCE['id'], $report->getCallTypes(), true);
}
public function hasCallTypeDialer(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_DIALER['id'], $report->getCallTypes(), true);
}
// transformers
private function transformTeam(Team $team): array
{
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
return [];
}
return [
'id' => $team->getUuid(),
'name' => $team->getName(),
];
}
private function transformReportFullView(AutomatedReport $report): array
{
$base = $this->transformReportBase($report);
return $report->getType() === self::TYPE_ASK_JIMINNY
? $base + $this->transformAskJiminnyFields($report)
: $base + $this->transformStandardReportFields($report);
}
private function transformReportBase(AutomatedReport $report): array
{
return [
'id' => $report->getUuid(),
'organization' => $this->transformOrganization(team: $report->getTeam()),
'report_type' => $this->transformReportType($report->getType()),
'frequency' => $this->transformFrequency($report->getFrequency()),
];
}
private function transformStandardReportFields(AutomatedReport $report): array
{
$team = $report->getTeam();
return [
'report_enabled' => $report->getStatus(),
'start_date_period' => $report->getFrom()?->format('Y-m-d H:i:s'),
'end_date_period' => $report->getTo()?->format('Y-m-d H:i:s'),
'deal_value_min' => $report->getDealValueMin(),
'deal_value_max' => $report->getDealValueMax(),
'call_types' => $this->transformCallType($report->getCallTypes()),
'media_types' => $this->transformMediaTypes($report),
'call_duration_min' => $this->transformDurationToMinutes($report->getCallDurationMin()),
'call_duration_max' => $this->transformDurationToMinutes($report->getCallDurationMax()),
'teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'deal_at_call_stages' => $this->transformStages(team: $team, stagesIds: $report->getDealAtCallStages()),
'current_deal_stages' => $this->transformStages(team: $team, stagesIds: $report->getCurrentDealStages()),
'recipients' => $this->transformRecipients($report->getRecipients()),
'created_by' => $this->transformCreator($report->getCreator()),
'additional_prompt_input' => $report->getAdditionalPromptInput(),
'custom_name' => $report->getCustomName(),
'created_at' => $report->getCreatedAt()->format('Y-m-d H:i:s'),
'updated_at' => $report->getUpdatedAt()->format('Y-m-d H:i:s'),
'deleted_at' => $report->getDeletedAt()?->format('Y-m-d H:i:s'),
];
}
private function transformAskJiminnyFields(AutomatedReport $report): array
{
$team = $report->getTeam();
$creatorId = $report->getAttribute('created_by');
$explicitUserIds = array_values(array_filter(
$report->getRecipients()['users'] ?? [],
static fn ($id) => $id !== $creatorId
));
return [
'report_name' => $report->getCustomName(),
'enabled' => $report->getStatus(),
'share_teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'share_users' => $this->transformRecipients(['users' => $explicitUserIds]),
'saved_search' => $this->transformSafeSearch($report->getSavedSearch()),
'ask_jiminny_prompt' => $this->transformAskJiminnyPrompt($report->getAskAnythingPrompt()),
'expires_on' => $report->getExpiresAt()?->format('Y-m-d'),
];
}
private function transformOrganization(?Team $team): array
{
return [
'id' => $team?->getUuid(),
'name' => $team?->getName(),
];
}
private function transformReportType(string $type): array
{
foreach (self::ALL_TYPES as $typeItem) {
if ($typeItem['id'] === $type) {
return $typeItem;
}
}
return [];
}
private function transformCallType(array $types): array
{
$result = [];
$callTypes = [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER];
foreach ($types as $type) {
foreach ($callTypes as $callTypeItem) {
if ($callTypeItem['id'] === $type) {
$result[] = $callTypeItem;
break;
}
}
}
return $result;
}
private function transformMediaTypes(AutomatedReport $report): array
{
$values = [];
foreach ($report->getMediaTypes() as $mediaType) {
if (! in_array($mediaType, self::MEDIA_TYPES, true)) {
continue;
}
$values[] = match ($mediaType) {
self::MEDIA_TYPE_PDF => self::MEDIA_TYPE_OBJECT_PDF,
self::MEDIA_TYPE_PODCAST => self::MEDIA_TYPE_OBJECT_PODCAST,
};
}
return $values;
}
private function transformFrequency(string $frequency): array
{
foreach (self::ALL_FREQUENCIES as $frequencyItem) {
if ($frequencyItem['id'] === $frequency) {
return $frequencyItem;
}
}
return [];
}
public function transformDurationToMinutes(?int $duration): ?int
{
if (! $duration) {
return null;
}
return (int) ($duration / 60);
}
private function transformGroups(?Team $team, array $groupsIds): array
{
if (empty($groupsIds) || ! $team) {
return [];
}
$data = [];
foreach ($groupsIds as $groupId) {
$group = $team->groups()->where('id', $groupId)->first();
if ($group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
'photoUrl' => $group->getPhotoUrl(),
];
}
}
return $data;
}
private function transformStages(?Team $team, array $stagesIds): array
{
if (empty($stagesIds) || ! $team) {
return [];
}
$data = [];
foreach ($stagesIds as $stageId) {
$stage = $team->stages()->where('id', $stageId)->first();
if ($stage) {
$data[] = [
'id' => $stage->getUuid(),
'name' => $stage->getName(),
];
}
}
return $data;
}
private function transformRecipients(array $recipients): array
{
$users = [];
foreach ($recipients['users'] ?? [] as $userId) {
$users[] = $this->transformUser($userId);
}
return $users;
}
private function transformCreator(?User $user): ?array
{
if ($user === null) {
return null;
}
return $this->transformUser($user->getId());
}
private function transformAskJiminnyPrompt(?AskAnythingPrompt $prompt): ?array
{
if ($prompt === null) {
return null;
}
return [
'id' => $prompt->getUuid(),
'name' => $prompt->getTitle(),
];
}
private function transformSafeSearch(?Search $search): ?array
{
if ($search === null) {
return null;
}
return [
'id' => $search->getUuid(),
'name' => $search->getName(),
];
}
private function transformUser(int $userId): array
{
/* @var ?User $user */
$user = $this->userRepository->find($userId);
return [
'id' => $user?->getUuid(),
'name' => $user?->getName(),
'email' => $user?->getEmailAddress(),
'photoUrl' => $user?->getPhotoUrl(),
];
}
public function create(array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$validatedData['created_by'] = auth()->id();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
public function update(string $uuid, array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$report = $this->automatedReportsRepository->findByUuid($uuid);
if (! $report) {
throw new InvalidArgumentException('Report not found');
}
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
/**
* Create an Ask Jiminny report.
*/
public function createAskJiminnyReport(array $data, User $creator): array
{
$validatedData = $this->validateAskJiminnyReportData($data, $creator);
$validatedData['created_by'] = $creator->getId();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
return $this->transformReportFullView($automatedReport);
}
/**
* Update an Ask Jiminny report.
*/
public function updateAskJiminnyReport(AutomatedReport $report, array $data, User $user): array
{
if (! $report->isAskJiminnyReport()) {
throw new InvalidArgumentException('Report is not an Ask Jiminny report');
}
$validatedData = $this->validateAskJiminnyReportData($data, $user);
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
return $this->transformReportFullView($automatedReport);
}
public function updateAskJiminnyReportStatus(AutomatedReport $report, bool $status): array
{
$this->automatedReportsRepository->update($report, ['status' => $status]);
return $this->transformReportFullView($report->fresh());
}
/**
* Validate and transform data for Ask Jiminny reports.
*/
private function validateAskJiminnyReportData(array $data, User $user): array
{
// Validate name
$name = trim($data['report_name'] ?? '');
if (empty($name)) {
throw new InvalidArgumentException('Report name is required');
}
if (mb_strlen($name) > 50) {
throw new InvalidArgumentException('Report name must be 50 characters or less');
}
// Validate frequency (only daily, weekly, monthly for Ask Jiminny)
$frequency = $data['frequency'] ?? null;
$askJiminnyFrequencies = [self::FREQUENCY_DAILY, self::FREQUENCY_WEEKLY, self::FREQUENCY_MONTHLY];
if (! in_array($frequency, $askJiminnyFrequencies, true)) {
throw new InvalidArgumentException('Frequency must be daily, weekly, or monthly');
}
// Validate expiration date
$expiresAt = $data['expires_on'] ?? null;
if (empty($expiresAt)) {
throw new InvalidArgumentException('Expiration date is required');
}
try {
$expiresAtDate = Carbon::parse($expiresAt);
} catch (InvalidFormatException $e) {
throw new InvalidArgumentException('Expiration date format is invalid');
}
$maxExpiration = Carbon::now()->addYear()->endOfDay();
if ($expiresAtDate->gt($maxExpiration)) {
throw new InvalidArgumentException('Expiration date cannot be more than 1 year from now');
}
if ($expiresAtDate->isPast()) {
throw new InvalidArgumentException('Expiration date cannot be in the past');
}
// Validate saved search
$activitySearchId = $data['saved_search'] ?? null;
if (empty($activitySearchId)) {
throw new InvalidArgumentException('Saved search is required');
}
$savedSearch = $this->activitySearchRepository->findByUuidAndUser($activitySearchId, $user);
if (! $savedSearch) {
throw new InvalidArgumentException('Saved search not found or does not belong to you');
}
// Validate saved prompt
$askAnythingPromptId = $data['ask_jiminny_prompt'] ?? null;
if (empty($askAnythingPromptId)) {
throw new InvalidArgumentException('Ask Jiminny prompt is required');
}
$prompt = $this->askAnythingRepository->getPromptByUuid($askAnythingPromptId);
if (! $prompt) {
throw new InvalidArgumentException('Ask Jiminny prompt not found');
}
// Validate status
$status = $data['enabled'] ?? false;
$recipientUserIds = [$user->getId()];
if (! empty($data['share_users'])) {
$sharedUserIds = $this->validateAndGetUserIdsByTeam(
$user->team,
(array) $data['share_users']
);
$recipientUserIds = array_merge($recipientUserIds, $sharedUserIds);
}
$sharedGroupIds = [];
if (! empty($data['share_teams'])) {
$sharedGroupIds = $this->validateAndGetGroupIds($user->team, (array) $data['share_teams']);
}
$recipientUserIds = array_values(array_unique($recipientUserIds));
return [
'team_id' => $user->getTeamId(),
'type' => self::TYPE_ASK_JIMINNY,
'status' => (bool) $status,
'frequency' => $frequency,
'custom_name' => $name,
'activity_search_id' => $savedSearch->getId(),
'ask_anything_prompt_id' => $prompt->getId(),
'expires_at' => $expiresAtDate->toDateString(),
'media_types' => [self::MEDIA_TYPE_PDF],
'call_types' => [],
'recipients' => ['users' => $recipientUserIds],
'groups' => $sharedGroupIds,
];
}
public static function getAskJiminnyFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::ASK_JIMINNY_FREQUENCIES);
}
public function getAskJiminnyReportFilters(User $user): array
{
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
return [
[
'id' => 'prompt',
'label' => 'Prompt',
'options' => $prompts,
],
[
'id' => 'saved_search',
'label' => 'Saved Search',
'options' => $savedSearches,
],
];
}
public function getAskJiminnyReportFormData(User $user, ?AutomatedReport $report = null): array
{
$team = $user->getTeam();
$userTimezone = $user->getTimezone();
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
$teamGroups = $this->groupRepository->getAllByTeam($team)->map(fn ($group) => [
'id' => $group->getUuid(),
'name' => $group->getName(),
])->values()->all();
$shareUsers = $this->recipientsService->getRecipientsFieldData(team: $team)['options'] ?? [];
$sharedTeamsValue = [];
$sharedUsersValue = [];
if ($report) {
$sharedTeamsValue = $this->transformGroups($team, $report->getGroups());
$recipientUserIds = $report->getRecipients()['users'] ?? [];
$creatorId = $report->getAttribute('created_by');
$sharedUserIds = array_values(array_filter(
$recipientUserIds,
static fn ($id) => $id !== $creatorId
));
$sharedUsersValue = collect($sharedUserIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (User $u) => [
'id' => $u->getUuid(),
'name' => $u->getName(),
])
->values()
->all();
}
return [
'fields' => [
[
'id' => 'enabled',
'inputType' => InputTypeEnum::TOGGLE,
'label' => '',
'value' => $report?->getStatus() ?? false,
],
[
'id' => 'report_name',
'inputType' => InputTypeEnum::TEXT,
'label' => 'Name',
'placeholder' => 'Enter name',
'required' => true,
'validation' => ['maxLength' => 50],
'value' => $report?->getCustomName() ?? '',
],
[
'id' => 'frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'label' => 'Frequency',
'required' => true,
'placeholder' => 'Select',
'options' => self::ASK_JIMINNY_FREQUENCIES,
'value' => $report ? $this->transformFrequency($report->getFrequency()) : null,
],
[
'id' => 'expires_on',
'inputType' =>...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71576
|
|
71577
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, 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
102
3
34
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Kiosk\AutomatedReports;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\ActivitySearch\FilterDefinition\InputTypeEnum;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\ApplicationException;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Contracts\UserContract;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Throwable;
class AutomatedReportsService
{
public const string TYPE_LOSS_ANALYSIS = 'loss_analysis';
public const string TYPE_ASK_JIMINNY = 'ask_jiminny';
/**
* Standard report types (used by kiosk for existing automated reports).
*/
// @TODO this will add filter, however if we need to control feature by FF we need conditional logic
public const array TYPES = [
['id' => 'exec_summary', 'name' => 'Exec Summary'],
['id' => 'coaching_profiles', 'name' => 'Coaching Profiles'],
['id' => 'product_feedback', 'name' => 'Product Feedback'],
['id' => self::TYPE_LOSS_ANALYSIS, 'name' => 'Loss Analysis'],
// ['id' => 'questions', 'name' => 'Questions'],
// ['id' => 'statistical_quant', 'name' => 'Statistical Quantitative'],
];
public const array ALL_TYPES = [
...self::TYPES,
['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'],
];
public const string FREQUENCY_DAILY = 'daily';
public const string FREQUENCY_WEEKLY = 'weekly';
public const string FREQUENCY_MONTHLY = 'monthly';
public const string FREQUENCY_QUARTERLY = 'quarterly';
public const string FREQUENCY_ONE_OFF = 'one_off';
/**
* Frequencies for standard (non-Ask Jiminny) reports.
*/
public const array FREQUENCIES = [
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
/**
* Frequencies for Ask Jiminny reports.
*/
public const array ASK_JIMINNY_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
];
public const string MEDIA_TYPE_PDF = 'pdf';
public const string MEDIA_TYPE_PODCAST = 'podcast';
public const array MEDIA_TYPES = [self::MEDIA_TYPE_PDF, self::MEDIA_TYPE_PODCAST];
public const array MEDIA_TYPE_OBJECT_PDF = ['id' => self::MEDIA_TYPE_PDF, 'name' => 'PDF'];
public const array MEDIA_TYPE_OBJECT_PODCAST = ['id' => self::MEDIA_TYPE_PODCAST, 'name' => 'Podcast'];
public const array MEDIA_TYPE_OBJECTS = [self::MEDIA_TYPE_OBJECT_PDF, self::MEDIA_TYPE_OBJECT_PODCAST];
public const array CALL_TYPE_CONFERENCE = ['id' => 'conference', 'name' => 'Conference'];
public const array CALL_TYPE_DIALER = ['id' => 'dialer', 'name' => 'Dialer'];
public const int SENT_REPORT_AT_HOURS = 5;
public const string PDF_KEY = 'pdf';
public const string AUDIO_KEY = 'audio';
private const array ALL_FREQUENCIES = [
['id' => self::FREQUENCY_DAILY, 'name' => 'Daily'],
['id' => self::FREQUENCY_WEEKLY, 'name' => 'Weekly'],
['id' => self::FREQUENCY_MONTHLY, 'name' => 'Monthly'],
['id' => self::FREQUENCY_QUARTERLY, 'name' => 'Quarterly'],
['id' => self::FREQUENCY_ONE_OFF, 'name' => 'One-off'],
];
private const string S3_DIR = 'reports';
private const array FILE_EXTENSIONS_VARIANTS = ['html', 'MD', 'pdf'];
private const array FILE_PODCAST_EXTENSIONS_VARIANTS = ['json', 'mp3', 'ssml'];
public function __construct(
private readonly TeamRepository $teamRepository,
private readonly GroupRepository $groupRepository,
private readonly UserRepository $userRepository,
private readonly StageRepository $stageRepository,
private readonly DealStagesService $dealStagesService,
private readonly RecipientsService $recipientsService,
private readonly AutomatedReportsRepository $automatedReportsRepository,
private readonly Webhook $webhookService,
private readonly BusDispatcher $dispatcher,
private readonly ActivityTypeService $activityTypeService,
private readonly PlaybookCategoryRepository $playbookCategoryRepository,
private readonly AskAnythingPromptService $askAnythingPromptService,
private readonly SearchRepository $activitySearchRepository,
private readonly AskAnythingRepository $askAnythingRepository,
) {
}
public static function getTypes(): array
{
$types = self::TYPES;
return array_map(static function ($type) {
return $type['id'];
}, $types);
}
public static function getCallTypes(): array
{
return array_map(static function ($callType) {
return $callType['id'];
}, [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER]);
}
public static function getFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::FREQUENCIES);
}
// front-facing structure
public function getReportEnabledFieldData(bool $value = false): array
{
return [
'id' => 'report_enabled',
'label' => '',
'inputType' => InputTypeEnum::TOGGLE,
'value' => $value,
];
}
// Organizations = Teams
public function getOrganizationFieldData(?string $value = null, bool $shortVersion = false): array
{
$options = $this->getTeams();
if ($shortVersion) {
return [
'id' => 'organization',
'label' => 'Organization',
'options' => $options,
];
}
return [
'id' => 'organization',
'label' => 'Organization',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $options,
'value' => $value,
'dependencies' => [
'teams',
'deal_stage_at_call',
'current_deal_stage',
'recipients',
ActivityTypeService::PLAYBOOK_CATEGORIES_KEY,
],
'dependsOn' => [],
];
}
// Teams = Groups
public function getTeamFieldData(array $options = [], array $value = [], bool $shortVersion = false): array
{
if ($shortVersion) {
return [
'id' => 'teams',
'label' => 'Team',
'options' => $options,
];
}
return [
'id' => 'teams',
'label' => 'Team',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => false,
'placeholder' => 'Select',
'options' => $options,
'value' => $value, // value should be an array of objects {id, name}
'dependencies' => [ActivityTypeService::PLAYBOOK_CATEGORIES_KEY],
'dependsOn' => [],
];
}
public function getReportTypeFieldData(?string $value = null, bool $shortVersion = false, ?Team $team = null): array
{
$types = [];
if ($team instanceof Team) {
if ($team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
$types = self::TYPES;
}
if ($team->hasFeature(FeatureEnum::ASK_JIMINNY_REPORTS)) {
$types[] = ['id' => self::TYPE_ASK_JIMINNY, 'name' => 'Ask Jiminny'];
}
} else {
$types = self::TYPES;
}
if ($shortVersion) {
return [
'id' => 'report_type',
'label' => 'Report Type',
'options' => $types,
];
}
return [
'id' => 'report_type',
'label' => 'Report Type',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => $types,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getFrequencyFieldData(?string $value = null): array
{
return [
'id' => 'frequency',
'label' => 'Frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'required' => true,
'placeholder' => 'Select',
'options' => self::FREQUENCIES,
'value' => $value,
'dependencies' => ['period'],
'dependsOn' => [],
];
}
public function getPeriodFieldData(?string $valueStartDate = null, ?string $valueEndDate = null): array
{
return [
'id' => 'period',
'label' => 'Select one-off period',
'inputType' => InputTypeEnum::DATE_RANGE,
'required' => true,
'placeholder' => 'Select',
'value' => ['startDate' => $valueStartDate, 'endDate' => $valueEndDate],
'queryParams' => [
'startDate' => 'start_date_period',
'endDate' => 'end_date_period',
],
'dependencies' => [],
'dependsOn' => ['frequency'],
];
}
public function getActivityTypesFieldData(?Team $team = null, array $value = [], array $teamsFilter = []): array
{
return $this->activityTypeService->getActivityTypeFieldData(team: $team, value: $value, groupIds: $teamsFilter);
}
public function getDealStageAtCallFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getDealStageAtCallFieldData(team: $team, value: $value);
}
public function getCurrentDealStageFieldData(?Team $team = null, array $value = []): array
{
return $this->dealStagesService->getCurrentDealStageFieldData(team: $team, value: $value);
}
public function getDealValueFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'deal_value',
'label' => 'Deal Value',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_deal_value',
'max' => 'max_deal_value',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallTypeFieldData(bool $conferenceOn = false, bool $dialerOn = false): array
{
$value = [];
$conferenceOn && $value[] = self::CALL_TYPE_CONFERENCE;
$dialerOn && $value[] = self::CALL_TYPE_DIALER;
return [
'id' => 'call_type',
'label' => 'Call Type',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => [
self::CALL_TYPE_CONFERENCE,
self::CALL_TYPE_DIALER,
],
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getMediaTypeFieldData(?AutomatedReport $report = null): array
{
$value = [];
if ($report) {
$value = $this->transformMediaTypes($report);
}
return [
'id' => 'media_types',
'label' => 'Export as',
'inputType' => InputTypeEnum::DROPDOWN_MULTIPLE,
'required' => true,
'options' => self::MEDIA_TYPE_OBJECTS,
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCallDurationFieldData(?int $valueMin = null, ?int $valueMax = null): array
{
return [
'id' => 'call_duration',
'label' => 'Call Duration',
'inputType' => InputTypeEnum::INTEGER_RANGE,
'required' => false,
'value' => ['min' => $valueMin, 'max' => $valueMax],
'queryParams' => [
'min' => 'min_call_duration',
'max' => 'max_call_duration',
],
'dependencies' => [],
'dependsOn' => [],
];
}
public function getRecipientsFieldData(?Team $team = null, array $value = []): array
{
return $this->recipientsService->getRecipientsFieldData(team: $team, value: $value);
}
public function getJiminnyRecipientsFieldData(array $value = []): array
{
return $this->recipientsService->getJiminnyRecipientsFieldData($value);
}
public function getAdditionalPromptInputFieldData(?string $value = null): array
{
return [
'id' => 'additional_prompt_input',
'label' => 'Special requirements',
'inputType' => InputTypeEnum::TEXTAREA,
'required' => false,
'placeholder' => 'What should be the focus of the report?',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
public function getCustomReportNameFieldData(?string $value = null): array
{
return [
'id' => 'custom_name',
'label' => 'Custom report name',
'inputType' => InputTypeEnum::TEXT,
'required' => false,
'placeholder' => 'Enter custom name',
'value' => $value,
'dependencies' => [],
'dependsOn' => [],
];
}
// data providers
public function getTeams(): array
{
$teams = $this->teamRepository->getTeamsForKiosk(status: Team::STATUS_ACTIVE);
$teamData = [];
foreach ($teams as $team) {
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
continue;
}
$teamData[] = $this->transformTeam($team);
}
return $teamData;
}
public function getTeamGroups(string $teamUuid): array
{
$data = [];
$team = $this->getTeam($teamUuid);
if ($team !== null) {
$groups = $team->groups()->get();
foreach ($groups as $group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
];
}
}
return $data;
}
public function getTeamsGroupsOptions(array $filterTeamUuids = []): array
{
$data = [];
$teams = $this->getTeams();
foreach ($teams as $team) {
if (! empty($filterTeamUuids) && ! in_array($team['id'], $filterTeamUuids, true)) {
continue;
}
$data[] = [
'label' => $team['name'],
'groups' => $this->getTeamGroups($team['id']),
];
}
return $data;
}
public function getTeam(string $teamUuid): ?Team
{
return $this->teamRepository->idOrUuid($teamUuid);
}
public function getTeamById(int $teamId): ?Team
{
return $this->teamRepository->find($teamId);
}
public function getGroupsUuids(AutomatedReport $report): array
{
$uuids = [];
$reportGroups = $report->getGroups();
foreach ($reportGroups as $groupId) {
if ($group = $this->groupRepository->find($groupId)) {
$uuids[] = $group->getUuid();
}
}
return $uuids;
}
public function getPlaybookCategoriesUuids(AutomatedReport $report): array
{
$uuids = [];
$playbookCategories = $report->getPlaybookCategories();
foreach ($playbookCategories as $id) {
if ($category = $this->playbookCategoryRepository->find($id)) {
$uuids[] = $category->getUuid();
}
}
return $uuids;
}
public function getDealAtCallStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getDealAtCallStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getCurrentDealStagesUuids(AutomatedReport $report): array
{
$uuids = [];
$reportStages = $report->getCurrentDealStages();
foreach ($reportStages as $id) {
if ($stage = $this->stageRepository->find($id)) {
$uuids[] = $stage->getUuid();
}
}
return $uuids;
}
public function getUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getRecipients());
}
public function getJiminnyUsersUuids(AutomatedReport $report): array
{
return $this->extractUserUuids($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function extractUserUuids(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => $user->getUuid())
->values()
->all();
}
// get mail data
public function getRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getRecipients());
}
/**
* @return array<UserContract>
*/
public function getRecipientUserObjects(AutomatedReport $report): array
{
$userIds = $report->getRecipients()['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->values()
->all();
}
private function getJiminnyRecipientUsers(AutomatedReport $report): array
{
return $this->buildRecipientUsers($report->getJiminnyRecipients());
}
/**
* @param array<string, mixed> $recipients
*/
private function buildRecipientUsers(array $recipients): array
{
$userIds = $recipients['users'] ?? [];
return collect($userIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (UserContract $user) => [
'email' => $user->getEmailAddress(),
'name' => $user->getName(),
'timezone' => $user->getTimezone()->getName(),
])
->values()
->all();
}
public function getValidRecipientUsers(AutomatedReport $report, bool $includeJiminny = false): array
{
if ($report->isAskJiminnyReport()) {
$recipients = $this->resolveAskJiminnyRecipients($report);
} else {
$recipients = $this->getRecipientUsers($report);
if ($includeJiminny) {
$recipients = array_merge($recipients, $this->getJiminnyRecipientUsers($report));
}
}
$emails = [];
return array_values(array_filter(
$recipients,
static function ($recipient) use (&$emails) {
if (empty($recipient['email']) || in_array($recipient['email'], $emails, true)) {
return false;
}
$emails[] = $recipient['email'];
return true;
}
));
}
private function resolveAskJiminnyRecipients(AutomatedReport $report): array
{
$recipients = [];
$creator = $report->getCreator();
if ($creator !== null) {
$recipients[] = [
'email' => $creator->getEmailAddress(),
'name' => $creator->getName(),
'timezone' => $creator->getTimezone()->getName(),
];
}
return array_merge(
$recipients,
$this->buildRecipientUsers($report->getRecipients()),
$this->getGroupRecipientUsers($report),
);
}
private function getGroupRecipientUsers(AutomatedReport $report): array
{
$users = [];
foreach ($report->getGroups() as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group === null) {
continue;
}
foreach ($group->getMembers() as $member) {
$users[] = [
'email' => $member->getEmailAddress(),
'name' => $member->getName(),
'timezone' => $member->getTimezone()->getName(),
];
}
}
return $users;
}
public function getReportTypeName(AutomatedReportResult $report): string
{
$type = $report->getReport()->getType();
$getType = $this->transformReportType($type);
return $getType['name'];
}
public function getReportPeriodName(AutomatedReportResult $report): string
{
$from = $report->getFromDate();
$to = $report->getToDate();
$frequency = $report->getReport()->getFrequency();
if ($from === null || $to === null) {
if (! $report->getReport()->isAskJiminnyReport()) {
$invalidPeriod = $from === null ? 'from' : 'to';
throw new ApplicationException('Report period is invalid: ' . $invalidPeriod);
}
$period = $this->calculateFromAndToDatePeriod($frequency);
$from = $period['fromDate'];
$to = $period['toDate'];
}
return $this->formatReportPeriodName($frequency, $from, $to);
}
private function formatReportPeriodName(string $frequency, Carbon $from, Carbon $to): string
{
$fromYear = $from->format('Y');
$toYear = $to->format('Y');
$differentYears = $fromYear !== $toYear;
switch ($frequency) {
case self::FREQUENCY_DAILY:
return $from->format('j M Y');
case self::FREQUENCY_QUARTERLY:
// 'Jan-Mar 2025' or 'Nov 2024-Jan 2025' if years differ
$startMonth = $from->format('M');
$endMonth = $to->copy()->subMonth();
$endMonthName = $endMonth->format('M');
$endMonthYear = $endMonth->format('Y');
if ($differentYears) {
return "{$startMonth} {$fromYear} - {$endMonthName} {$endMonthYear}";
}
return "{$startMonth} - {$endMonthName} {$toYear}";
case self::FREQUENCY_MONTHLY:
// 'May 2025' - monthly reports are always within the same year
return $from->format('M Y');
case self::FREQUENCY_WEEKLY:
// '4 - 8 Aug 2025', '27 Oct - 3 Nov 2025', or '28 Dec 2024 - 3 Jan 2025' if years differ
$startDay = $from->format('j');
$endDay = $to->format('j');
$startMonth = $from->format('M');
$endMonth = $to->format('M');
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
if ($startMonth !== $endMonth) {
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
}
return "{$startDay} - {$endDay} {$endMonth} {$toYear}";
case self::FREQUENCY_ONE_OFF:
// '2 May-31 May 2025' or '15 Dec 2024-15 Jan 2025' if years differ
$startDay = $from->format('j');
$startMonth = $from->format('M');
$endDay = $to->format('j');
$endMonth = $to->format('M');
// If same month and year, use a format like '2-31 May 2025'
if ($startMonth === $endMonth && ! $differentYears) {
return "{$startDay} - {$endDay} {$startMonth} {$toYear}";
}
// If different years, include both years
if ($differentYears) {
return "{$startDay} {$startMonth} {$fromYear} - {$endDay} {$endMonth} {$toYear}";
}
// Same year but different months
return "{$startDay} {$startMonth} - {$endDay} {$endMonth} {$toYear}";
default:
// Default format for unknown frequencies
return $from->format('j M Y') . ' - ' . $to->format('j M Y');
}
}
public function getReportTeamsName(AutomatedReportResult $report): string
{
$groups = $report->getGroups();
if (empty($groups)) {
return 'All';
}
// Get group names from repository
$groupNames = [];
foreach ($groups as $groupId) {
$group = $this->groupRepository->find($groupId);
if ($group) {
$groupNames[] = $group->getName();
}
}
if (count($groupNames) === 1) {
// Single team format
$teamsName = $groupNames[0];
} else {
// Multiple teams format
$teamsName = implode(', ', $groupNames);
}
return $teamsName;
}
public function getReportFileName(AutomatedReportResult $report): string
{
$customName = $report->getReport()->getCustomName();
$periodName = $this->getReportPeriodName($report);
$filenameSuffix = $this->getFilenameSuffix($report);
if ($customName) {
if ($filenameSuffix) {
$customName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$customName} - {$periodName}");
}
$baseName = $this->getReportTypeName($report);
if ($filenameSuffix) {
$baseName .= " {$filenameSuffix}";
}
return $this->sanitizeFileName("{$baseName} - {$periodName} - {$this->getReportTeamsName($report)}");
}
public function getReportFileNameWithExtension(AutomatedReportResult $result): string
{
$extension = $this->getMediaTypeMetadata($result)['extension'];
return $this->getReportFileName($result) . '.' . $extension;
}
public function sanitizeFileName(string $fileName): string
{
return str_replace(['/', '\\'], '-', $fileName);
}
public function isUserRecipientOfReport(User $user, AutomatedReport $report): bool
{
$recipientIds = array_map('intval', $report->getRecipients()['users'] ?? []);
if (in_array($user->getId(), $recipientIds, true)) {
return true;
}
if ($report->isAskJiminnyReport()) {
$groupId = $user->getGroupId();
if ($groupId !== null && in_array($groupId, $report->getGroups(), true)) {
return true;
}
}
return false;
}
public function transformReportResults(Collection $automatedReportResults): array
{
$data = [];
foreach ($automatedReportResults as $automatedReportResult) {
/** @var AutomatedReportResult $automatedReportResult */
$report = $automatedReportResult->getReport();
$createdBy = $report->getCreator();
$creator = [
'id' => $createdBy?->getUuid(),
'name' => $createdBy?->getName(),
'email' => $createdBy?->getEmailAddress(),
'photoUrl' => $createdBy?->getPhotoUrl(),
];
$data[] = [
'id' => $automatedReportResult->getUuid(),
'name' => $automatedReportResult->getName(),
'frequency' => $this->transformFrequency($report->getFrequency()),
'recipients' => $this->buildRecipients($report),
'report_type' => $this->transformReportType($report->getType()),
'media_type' => $automatedReportResult->getMediaType(),
'downloadUrl' => $this->generateReportResultDownloadUrl($automatedReportResult),
'viewUrl' => $this->generateReportResultViewUrl($automatedReportResult),
'generated_at' => $automatedReportResult->getGeneratedAt()?->toIso8601String(),
'creator' => $creator,
];
}
return $data;
}
private function buildRecipients(AutomatedReport $report): array
{
$creatorUuid = $report->getCreator()?->getUuid();
$recipients = array_values(array_filter(
$this->transformRecipients($report->getRecipients()),
static fn (array $recipient): bool => $recipient['id'] !== $creatorUuid,
));
if (! $report->isAskJiminnyReport()) {
return $recipients;
}
return [
...array_values($this->transformGroups(team: $report->getTeam(), groupsIds: $report->getGroups())),
...$recipients,
];
}
public function hasCallTypeConference(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_CONFERENCE['id'], $report->getCallTypes(), true);
}
public function hasCallTypeDialer(AutomatedReport $report): bool
{
return in_array(self::CALL_TYPE_DIALER['id'], $report->getCallTypes(), true);
}
// transformers
private function transformTeam(Team $team): array
{
if (! $team->hasFeature(FeatureEnum::AUTOMATED_REPORTS)) {
return [];
}
return [
'id' => $team->getUuid(),
'name' => $team->getName(),
];
}
private function transformReportFullView(AutomatedReport $report): array
{
$base = $this->transformReportBase($report);
return $report->getType() === self::TYPE_ASK_JIMINNY
? $base + $this->transformAskJiminnyFields($report)
: $base + $this->transformStandardReportFields($report);
}
private function transformReportBase(AutomatedReport $report): array
{
return [
'id' => $report->getUuid(),
'organization' => $this->transformOrganization(team: $report->getTeam()),
'report_type' => $this->transformReportType($report->getType()),
'frequency' => $this->transformFrequency($report->getFrequency()),
];
}
private function transformStandardReportFields(AutomatedReport $report): array
{
$team = $report->getTeam();
return [
'report_enabled' => $report->getStatus(),
'start_date_period' => $report->getFrom()?->format('Y-m-d H:i:s'),
'end_date_period' => $report->getTo()?->format('Y-m-d H:i:s'),
'deal_value_min' => $report->getDealValueMin(),
'deal_value_max' => $report->getDealValueMax(),
'call_types' => $this->transformCallType($report->getCallTypes()),
'media_types' => $this->transformMediaTypes($report),
'call_duration_min' => $this->transformDurationToMinutes($report->getCallDurationMin()),
'call_duration_max' => $this->transformDurationToMinutes($report->getCallDurationMax()),
'teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'deal_at_call_stages' => $this->transformStages(team: $team, stagesIds: $report->getDealAtCallStages()),
'current_deal_stages' => $this->transformStages(team: $team, stagesIds: $report->getCurrentDealStages()),
'recipients' => $this->transformRecipients($report->getRecipients()),
'created_by' => $this->transformCreator($report->getCreator()),
'additional_prompt_input' => $report->getAdditionalPromptInput(),
'custom_name' => $report->getCustomName(),
'created_at' => $report->getCreatedAt()->format('Y-m-d H:i:s'),
'updated_at' => $report->getUpdatedAt()->format('Y-m-d H:i:s'),
'deleted_at' => $report->getDeletedAt()?->format('Y-m-d H:i:s'),
];
}
private function transformAskJiminnyFields(AutomatedReport $report): array
{
$team = $report->getTeam();
$creatorId = $report->getAttribute('created_by');
$explicitUserIds = array_values(array_filter(
$report->getRecipients()['users'] ?? [],
static fn ($id) => $id !== $creatorId
));
return [
'report_name' => $report->getCustomName(),
'enabled' => $report->getStatus(),
'share_teams' => $this->transformGroups(team: $team, groupsIds: $report->getGroups()),
'share_users' => $this->transformRecipients(['users' => $explicitUserIds]),
'saved_search' => $this->transformSafeSearch($report->getSavedSearch()),
'ask_jiminny_prompt' => $this->transformAskJiminnyPrompt($report->getAskAnythingPrompt()),
'expires_on' => $report->getExpiresAt()?->format('Y-m-d'),
];
}
private function transformOrganization(?Team $team): array
{
return [
'id' => $team?->getUuid(),
'name' => $team?->getName(),
];
}
private function transformReportType(string $type): array
{
foreach (self::ALL_TYPES as $typeItem) {
if ($typeItem['id'] === $type) {
return $typeItem;
}
}
return [];
}
private function transformCallType(array $types): array
{
$result = [];
$callTypes = [self::CALL_TYPE_CONFERENCE, self::CALL_TYPE_DIALER];
foreach ($types as $type) {
foreach ($callTypes as $callTypeItem) {
if ($callTypeItem['id'] === $type) {
$result[] = $callTypeItem;
break;
}
}
}
return $result;
}
private function transformMediaTypes(AutomatedReport $report): array
{
$values = [];
foreach ($report->getMediaTypes() as $mediaType) {
if (! in_array($mediaType, self::MEDIA_TYPES, true)) {
continue;
}
$values[] = match ($mediaType) {
self::MEDIA_TYPE_PDF => self::MEDIA_TYPE_OBJECT_PDF,
self::MEDIA_TYPE_PODCAST => self::MEDIA_TYPE_OBJECT_PODCAST,
};
}
return $values;
}
private function transformFrequency(string $frequency): array
{
foreach (self::ALL_FREQUENCIES as $frequencyItem) {
if ($frequencyItem['id'] === $frequency) {
return $frequencyItem;
}
}
return [];
}
public function transformDurationToMinutes(?int $duration): ?int
{
if (! $duration) {
return null;
}
return (int) ($duration / 60);
}
private function transformGroups(?Team $team, array $groupsIds): array
{
if (empty($groupsIds) || ! $team) {
return [];
}
$data = [];
foreach ($groupsIds as $groupId) {
$group = $team->groups()->where('id', $groupId)->first();
if ($group) {
$data[] = [
'id' => $group->getUuid(),
'name' => $group->getName(),
'photoUrl' => $group->getPhotoUrl(),
];
}
}
return $data;
}
private function transformStages(?Team $team, array $stagesIds): array
{
if (empty($stagesIds) || ! $team) {
return [];
}
$data = [];
foreach ($stagesIds as $stageId) {
$stage = $team->stages()->where('id', $stageId)->first();
if ($stage) {
$data[] = [
'id' => $stage->getUuid(),
'name' => $stage->getName(),
];
}
}
return $data;
}
private function transformRecipients(array $recipients): array
{
$users = [];
foreach ($recipients['users'] ?? [] as $userId) {
$users[] = $this->transformUser($userId);
}
return $users;
}
private function transformCreator(?User $user): ?array
{
if ($user === null) {
return null;
}
return $this->transformUser($user->getId());
}
private function transformAskJiminnyPrompt(?AskAnythingPrompt $prompt): ?array
{
if ($prompt === null) {
return null;
}
return [
'id' => $prompt->getUuid(),
'name' => $prompt->getTitle(),
];
}
private function transformSafeSearch(?Search $search): ?array
{
if ($search === null) {
return null;
}
return [
'id' => $search->getUuid(),
'name' => $search->getName(),
];
}
private function transformUser(int $userId): array
{
/* @var ?User $user */
$user = $this->userRepository->find($userId);
return [
'id' => $user?->getUuid(),
'name' => $user?->getName(),
'email' => $user?->getEmailAddress(),
'photoUrl' => $user?->getPhotoUrl(),
];
}
public function create(array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$validatedData['created_by'] = auth()->id();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
public function update(string $uuid, array $data): array
{
$validatedData = $this->validateAndTransformData($data);
$report = $this->automatedReportsRepository->findByUuid($uuid);
if (! $report) {
throw new InvalidArgumentException('Report not found');
}
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
$this->generateOneOffReport($automatedReport);
return $this->transformReportFullView($automatedReport);
}
/**
* Create an Ask Jiminny report.
*/
public function createAskJiminnyReport(array $data, User $creator): array
{
$validatedData = $this->validateAskJiminnyReportData($data, $creator);
$validatedData['created_by'] = $creator->getId();
$automatedReport = $this->automatedReportsRepository->create($validatedData);
return $this->transformReportFullView($automatedReport);
}
/**
* Update an Ask Jiminny report.
*/
public function updateAskJiminnyReport(AutomatedReport $report, array $data, User $user): array
{
if (! $report->isAskJiminnyReport()) {
throw new InvalidArgumentException('Report is not an Ask Jiminny report');
}
$validatedData = $this->validateAskJiminnyReportData($data, $user);
$oldCustomName = $report->getCustomName();
$automatedReport = $this->automatedReportsRepository->update($report, $validatedData);
if ($oldCustomName !== $automatedReport->getCustomName()) {
$this->updateResultNames($automatedReport);
}
return $this->transformReportFullView($automatedReport);
}
public function updateAskJiminnyReportStatus(AutomatedReport $report, bool $status): array
{
$this->automatedReportsRepository->update($report, ['status' => $status]);
return $this->transformReportFullView($report->fresh());
}
/**
* Validate and transform data for Ask Jiminny reports.
*/
private function validateAskJiminnyReportData(array $data, User $user): array
{
// Validate name
$name = trim($data['report_name'] ?? '');
if (empty($name)) {
throw new InvalidArgumentException('Report name is required');
}
if (mb_strlen($name) > 50) {
throw new InvalidArgumentException('Report name must be 50 characters or less');
}
// Validate frequency (only daily, weekly, monthly for Ask Jiminny)
$frequency = $data['frequency'] ?? null;
$askJiminnyFrequencies = [self::FREQUENCY_DAILY, self::FREQUENCY_WEEKLY, self::FREQUENCY_MONTHLY];
if (! in_array($frequency, $askJiminnyFrequencies, true)) {
throw new InvalidArgumentException('Frequency must be daily, weekly, or monthly');
}
// Validate expiration date
$expiresAt = $data['expires_on'] ?? null;
if (empty($expiresAt)) {
throw new InvalidArgumentException('Expiration date is required');
}
try {
$expiresAtDate = Carbon::parse($expiresAt);
} catch (InvalidFormatException $e) {
throw new InvalidArgumentException('Expiration date format is invalid');
}
$maxExpiration = Carbon::now()->addYear()->endOfDay();
if ($expiresAtDate->gt($maxExpiration)) {
throw new InvalidArgumentException('Expiration date cannot be more than 1 year from now');
}
if ($expiresAtDate->isPast()) {
throw new InvalidArgumentException('Expiration date cannot be in the past');
}
// Validate saved search
$activitySearchId = $data['saved_search'] ?? null;
if (empty($activitySearchId)) {
throw new InvalidArgumentException('Saved search is required');
}
$savedSearch = $this->activitySearchRepository->findByUuidAndUser($activitySearchId, $user);
if (! $savedSearch) {
throw new InvalidArgumentException('Saved search not found or does not belong to you');
}
// Validate saved prompt
$askAnythingPromptId = $data['ask_jiminny_prompt'] ?? null;
if (empty($askAnythingPromptId)) {
throw new InvalidArgumentException('Ask Jiminny prompt is required');
}
$prompt = $this->askAnythingRepository->getPromptByUuid($askAnythingPromptId);
if (! $prompt) {
throw new InvalidArgumentException('Ask Jiminny prompt not found');
}
// Validate status
$status = $data['enabled'] ?? false;
$recipientUserIds = [$user->getId()];
if (! empty($data['share_users'])) {
$sharedUserIds = $this->validateAndGetUserIdsByTeam(
$user->team,
(array) $data['share_users']
);
$recipientUserIds = array_merge($recipientUserIds, $sharedUserIds);
}
$sharedGroupIds = [];
if (! empty($data['share_teams'])) {
$sharedGroupIds = $this->validateAndGetGroupIds($user->team, (array) $data['share_teams']);
}
$recipientUserIds = array_values(array_unique($recipientUserIds));
return [
'team_id' => $user->getTeamId(),
'type' => self::TYPE_ASK_JIMINNY,
'status' => (bool) $status,
'frequency' => $frequency,
'custom_name' => $name,
'activity_search_id' => $savedSearch->getId(),
'ask_anything_prompt_id' => $prompt->getId(),
'expires_at' => $expiresAtDate->toDateString(),
'media_types' => [self::MEDIA_TYPE_PDF],
'call_types' => [],
'recipients' => ['users' => $recipientUserIds],
'groups' => $sharedGroupIds,
];
}
public static function getAskJiminnyFrequencies(): array
{
return array_map(static function ($frequency) {
return $frequency['id'];
}, self::ASK_JIMINNY_FREQUENCIES);
}
public function getAskJiminnyReportFilters(User $user): array
{
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
return [
[
'id' => 'prompt',
'label' => 'Prompt',
'options' => $prompts,
],
[
'id' => 'saved_search',
'label' => 'Saved Search',
'options' => $savedSearches,
],
];
}
public function getAskJiminnyReportFormData(User $user, ?AutomatedReport $report = null): array
{
$team = $user->getTeam();
$userTimezone = $user->getTimezone();
$savedSearches = $this->activitySearchRepository->findByUserOrderedByName($user)
->map(fn (Search $search) => [
'id' => $search->getUuid(),
'name' => $search->getName(),
])
->values()->all();
$prompts = collect(
$this->askAnythingPromptService->get($user, AskAnythingPromptTarget::on_demand)
)->map(fn (AskAnythingPromptDto $prompt) => [
'id' => $prompt->id,
'name' => $prompt->title,
])->values()->all();
$teamGroups = $this->groupRepository->getAllByTeam($team)->map(fn ($group) => [
'id' => $group->getUuid(),
'name' => $group->getName(),
])->values()->all();
$shareUsers = $this->recipientsService->getRecipientsFieldData(team: $team)['options'] ?? [];
$sharedTeamsValue = [];
$sharedUsersValue = [];
if ($report) {
$sharedTeamsValue = $this->transformGroups($team, $report->getGroups());
$recipientUserIds = $report->getRecipients()['users'] ?? [];
$creatorId = $report->getAttribute('created_by');
$sharedUserIds = array_values(array_filter(
$recipientUserIds,
static fn ($id) => $id !== $creatorId
));
$sharedUsersValue = collect($sharedUserIds)
->map(fn ($id) => $this->userRepository->find((int) $id))
->filter()
->map(fn (User $u) => [
'id' => $u->getUuid(),
'name' => $u->getName(),
])
->values()
->all();
}
return [
'fields' => [
[
'id' => 'enabled',
'inputType' => InputTypeEnum::TOGGLE,
'label' => '',
'value' => $report?->getStatus() ?? false,
],
[
'id' => 'report_name',
'inputType' => InputTypeEnum::TEXT,
'label' => 'Name',
'placeholder' => 'Enter name',
'required' => true,
'validation' => ['maxLength' => 50],
'value' => $report?->getCustomName() ?? '',
],
[
'id' => 'frequency',
'inputType' => InputTypeEnum::DROPDOWN,
'label' => 'Frequency',
'required' => true,
'placeholder' => 'Select',
'options' => self::ASK_JIMINNY_FREQUENCIES,
'value' => $report ? $this->transformFrequency($report->getFrequency()) : null,
],
[
'id' => 'expires_on',
'inputType' =>...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71577
|
|
71580
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Show Replace Field
Search History
LOG_PREFIX
New Line
Match Case
Words
Regex
Replace History
Replace
New Line
Preserve case
3/16
Previous Occurrence
Next Occurrence
Filter Search Results
Open in Window, Multiple Cursors
Click to highlight
Close
Code changed:
Hide
Sync Changes
Hide This Notification
1
3
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Psr\Log\LoggerInterface;
use Throwable;
class RequestGenerateAskJiminnyReportJob implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use Queueable;
private const string LOG_PREFIX = '[AskJiminnyReport:Generate]';
private const int MIN_ACTIVITIES_COUNT = 1;
public int $tries = 2;
private ?AutomatedReportResult $reportResult = null;
public function __construct(private readonly string $reportUuid)
{
$this->onQueue(Constants::QUEUE_ANALYTICS);
}
public function uniqueId(): string
{
return $this->reportUuid;
}
public function handle(
AutomatedReportsService $reportService,
AskJiminnyReportActivityService $activityService,
ProphetClient $prophetClient,
LoggerInterface $logger,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
): void {
$logger->info(self::LOG_PREFIX . ' Started', [
'automatedReportUuid' => $this->reportUuid,
]);
try {
$automatedReport = $reportService->getReport($this->reportUuid);
// $this->dispatchNotGeneratedNotifications(
// $automatedReport,
// $reportService,
// $urlGenerator,
// $jobDispatcher,
// $logger,
// );
//
// return;
if (! $this->validateReport($automatedReport, $logger)) {
return;
}
$creator = $automatedReport->getCreator();
if ($creator === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, report creator not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$savedSearch = $automatedReport->getSavedSearch();
if ($savedSearch === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, saved search not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$prompt = $automatedReport->getAskAnythingPrompt();
if ($prompt === null) {
$logger->warning(self::LOG_PREFIX . ' Skipped, ask anything prompt not found', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$this->reportResult = $reportService->getOrCreateReportResult(
automatedReport: $automatedReport,
data: [
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => AutomatedReportsService::MEDIA_TYPE_PDF,
]
);
$activityIds = $activityService->getActivityIdsForSavedSearch(
savedSearch: $savedSearch,
user: $creator,
frequency: $automatedReport->getFrequency(),
);
$logger->info(self::LOG_PREFIX . ' Fetched activity IDs', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
if (count($activityIds) < self::MIN_ACTIVITIES_COUNT) {
$this->failReport(AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES);
$logger->info(self::LOG_PREFIX . ' Not enough activities, skipped', [
'automatedReportUuid' => $this->reportUuid,
'activityCount' => count($activityIds),
]);
$this->dispatchNotGeneratedNotifications(
$automatedReport,
$reportService,
$urlGenerator,
$jobDispatcher,
$logger,
);
return;
}
$payload = $reportService->getAskJiminnyGenerateReportPayload(
automatedReport: $automatedReport,
reportResult: $this->reportResult,
activityIds: $activityIds,
);
$this->reportResult->update([
'name' => $reportService->getReportFileName($this->reportResult),
'payload' => $payload,
'status' => AutomatedReportResult::STATUS_REQUESTED,
'requested_at' => Carbon::now()->toDateTimeString(),
]);
$logger->info(self::LOG_PREFIX . ' Request sent', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult->getUuid(),
'payload' => $payload,
]);
$response = $prophetClient->sendRequest(
endpoint: ProphetClient::ASK_JIMINNY_REPORT,
requestArray: $payload,
);
$logger->info(self::LOG_PREFIX . ' Response received', [
'response' => $response->getContent(),
]);
} catch (Throwable $exception) {
$reason = $exception instanceof ProphetException
? AutomatedReportResult::REASON_PROPHET_API_ERROR
: AutomatedReportResult::REASON_DEFAULT;
$this->failReport($reason);
$logger->error(self::LOG_PREFIX . ' Error', [
'automatedReportUuid' => $this->reportUuid,
'reportUuid' => $this->reportResult?->getUuid(),
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
]);
if ($this->attempts() < $this->tries) {
$logger->info(self::LOG_PREFIX . ' Retry scheduled', [
'attempts' => $this->attempts(),
]);
$this->release(30);
} else {
$this->fail($exception);
}
}
}
private function validateReport(AutomatedReport $automatedReport, LoggerInterface $logger): bool
{
if ($automatedReport->getType() !== AutomatedReportsService::TYPE_ASK_JIMINNY) {
$logger->warning(self::LOG_PREFIX . ' Skipped, not an ask_jiminny report', [
'automatedReportUuid' => $this->reportUuid,
'type' => $automatedReport->getType(),
]);
return false;
}
if (! $automatedReport->getStatus()) {
$logger->info(self::LOG_PREFIX . ' Skipped, report is not active', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
if ($automatedReport->getTeam()->getStatus() !== Team::STATUS_ACTIVE) {
$logger->info(self::LOG_PREFIX . ' Skipped, team is inactive', [
'automatedReportUuid' => $this->reportUuid,
]);
return false;
}
return true;
}
private function failReport(int $reason): void
{
$this->reportResult?->update([
'status' => AutomatedReportResult::STATUS_FAILED,
'reason' => $reason,
]);
}
private function dispatchNotGeneratedNotifications(
AutomatedReport $automatedReport,
AutomatedReportsService $reportService,
UrlGenerator $urlGenerator,
JobDispatcherInterface $jobDispatcher,
LoggerInterface $logger,
): void {
if ($this->reportResult === null) {
return;
}
$recipients = $reportService->getValidRecipientUsers($automatedReport);
if (empty($recipients)) {
$logger->info(self::LOG_PREFIX . ' No recipients to notify about missing report', [
'automatedReportUuid' => $this->reportUuid,
]);
return;
}
$reportName = $automatedReport->getCustomName()
?: $reportService->getReportTypeName($this->reportResult);
$periodName = $reportService->getReportPeriodName($this->reportResult);
$reportsPageUrl = $urlGenerator->route('ai.reports.show');
foreach ($recipients as $recipient) {
$jobDispatcher->dispatch(new SendReportNotGeneratedMailJob(
reportUuid: $this->reportResult->getUuid(),
recipientEmail: $recipient['email'],
recipientName: $recipient['name'] ?? null,
reportName: $reportName,
periodName: $periodName,
reportsPageUrl: $reportsPageUrl,
));
}
$logger->info(self::LOG_PREFIX . ' Dispatched not-generated notifications', [
'automatedReportUuid' => $this->reportUuid,
'recipientsCount' => count($recipients),
]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
47
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 12:56:51] local.INFO: [automated-reports] Started {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Checking conditions {"isMonday":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Completed {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:52] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {"saved_search_id":1977,"user_id":143,"activity_count":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","recipientsCount":1} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [Send Report Not Generated Mail] Email sent {"uuid":"dcb12181-9de1-4ef0-9d45-fb4ea6fd0778","email":"[EMAIL]","recipientName":"Lukas Kovalik"} {"correlation_id":"fdf99c5e-56cc-4478-ab54-250fc09fb443","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:57:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"23c760c9-672e-4166-a16d-e55db7824744","trace_id":"683fff63-372b-4638-9ebe-d1b0d7cd47b9"}
[2026-04-22 12:57:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"23c760c9-672e-4166-a16d-e55db7824744","trace_id":"683fff63-372b-4638-9ebe-d1b0d7cd47b9"}
[2026-04-22 12:57:06] local.NOTICE: Monitoring start {"correlation_id":"93748590-5bcc-4b5e-82a2-9cd195faadc8","trace_id":"788e496b-9a54-46ef-a822-45932aabd9ac"}
[2026-04-22 12:57:06] local.NOTICE: Monitoring end {"correlation_id":"93748590-5bcc-4b5e-82a2-9cd195faadc8","trace_id":"788e496b-9a54-46ef-a822-45932aabd9ac"}
[2026-04-22 12:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"f7252a17-bc62-4f94-afe0-ccfd221e69b7","trace_id":"1592110b-35f8-420a-83a2-a24fb4d4a5fa"}
[2026-04-22 12:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"f7252a17-bc62-4f94-afe0-ccfd221e69b7","trace_id":"1592110b-35f8-420a-83a2-a24fb4d4a5fa"}
[2026-04-22 12:57:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:08] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:08] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:10] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:10] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:11] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"a4eecb2f-a606-43af-b9bd-0941ef3f15c1","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2893f84a-555c-4fc8-a8c8-128c7e3ffc21","trace_id":"7ba30dd2-e0d1-4b47-b23d-2ab8a2944007"}
[2026-04-22 12:58:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"2893f84a-555c-4fc8-a8c8-128c7e3ffc21","trace_id":"7ba30dd2-e0d1-4b47-b23d-2ab8a2944007"}
[2026-04-22 12:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2893f84a-555c-4fc8-a8c8-128c7e3ffc21","trace_id":"7ba30dd2-e0d1-4b47-b23d-2ab8a2944007"}
[2026-04-22 12:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4f659da1-6b2c-492a-bdb8-db3789f92e86","trace_id":"5f899a25-4450-47e9-ada0-a83001bd00be"}
[2026-04-22 12:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4f659da1-6b2c-492a-bdb8-db3789f92e86","trace_id":"5f899a25-4450-47e9-ada0-a83001bd00be"}
[2026-04-22 12:58:07] local.NOTICE: Monitoring start {"correlation_id":"c45f0584-bcf3-44c9-b9a2-7f6ea9ce66c0","trace_id":"ad240a18-b6d7-43d8-a9a6-d56613965203"}
[2026-04-22 12:58:07] local.NOTICE: Monitoring end {"correlation_id":"c45f0584-bcf3-44c9-b9a2-7f6ea9ce66c0","trace_id":"ad240a18-b6d7-43d8-a9a6-d56613965203"}
[2026-04-22 12:58:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4739d379-46ac-41e7-a396-1365033ae798","trace_id":"bb579ad2-7e23-4512-822c-2447def47d44"}
[2026-04-22 12:58:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4739d379-46ac-41e7-a396-1365033ae798","trace_id":"bb579ad2-7e23-4512-822c-2447def47d44"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
71580
|
|
72014
|
Project: faVsco.js, menu
JY-20157-AJ-report-not-se Project: faVsco.js, menu
JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
RequestGenerateAskJiminnyReportJobTest
Run 'RequestGenerateAskJiminnyReportJobTest'
Debug 'RequestGenerateAskJiminnyReportJobTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
1
16
4
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Jobs\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Contracts\Routing\UrlGenerator;
use Jiminny\Component\ProphetAi\Exceptions\ProphetException;
use Jiminny\Component\ProphetAi\ProphetClient;
use Jiminny\Jobs\AutomatedReports\RequestGenerateAskJiminnyReportJob;
use Jiminny\Jobs\AutomatedReports\SendReportNotGeneratedMailJob;
use Jiminny\Jobs\JobDispatcherInterface;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Services\Kiosk\AutomatedReports\AskJiminnyReportActivityService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class RequestGenerateAskJiminnyReportJobTest extends TestCase
{
private AutomatedReportsService&MockObject $reportService;
private AskJiminnyReportActivityService&MockObject $activityService;
private ProphetClient&MockObject $prophetClient;
private LoggerInterface&MockObject $logger;
private UrlGenerator&MockObject $urlGenerator;
private JobDispatcherInterface&MockObject $jobDispatcher;
protected function setUp(): void
{
$this->reportService = $this->createMock(AutomatedReportsService::class);
$this->activityService = $this->createMock(AskJiminnyReportActivityService::class);
$this->prophetClient = $this->createMock(ProphetClient::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->urlGenerator = $this->createMock(UrlGenerator::class);
$this->jobDispatcher = $this->createMock(JobDispatcherInterface::class);
}
private function makeJob(string $uuid = 'report-uuid'): RequestGenerateAskJiminnyReportJob
{
return new RequestGenerateAskJiminnyReportJob($uuid);
}
private function makeActiveReport(
string $type = AutomatedReportsService::TYPE_ASK_JIMINNY,
bool $status = true,
string $teamStatus = Team::STATUS_ACTIVE,
): AutomatedReport&MockObject { // @phpstan-ignore-line
$team = $this->createMock(Team::class);
$team->method('getStatus')->willReturn($teamStatus);
$report = $this->createMock(AutomatedReport::class);
$report->method('getType')->willReturn($type);
$report->method('getStatus')->willReturn($status);
$report->method('getTeam')->willReturn($team);
return $report;
}
public function testUniqueIdReturnsReportUuid(): void
{
$job = $this->makeJob('my-unique-uuid');
$this->assertEquals('my-unique-uuid', $job->uniqueId());
}
public function testHandleSkipsWhenReportTypeIsNotAskJiminny(): void
{
$report = $this->makeActiveReport(type: 'exec_summary');
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('info')
->with($this->stringContains('Started'));
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('not an ask_jiminny report'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenReportIsInactive(): void
{
$report = $this->makeActiveReport(status: false);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenTeamIsInactive(): void
{
$report = $this->makeActiveReport(teamStatus: Team::STATUS_DEACTIVATED);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->exactly(2))
->method('info');
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenCreatorIsNull(): void
{
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('report creator not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenSavedSearchIsNull(): void
{
$creator = $this->createMock(User::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('saved search not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSkipsWhenPromptIsNull(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn(null);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->logger->expects($this->once())
->method('warning')
->with($this->stringContains('ask anything prompt not found'));
$this->reportService->expects($this->never())->method('getOrCreateReportResult');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleFailsReportWhenNotEnoughActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_FAILED
&& $data['reason'] === AutomatedReportResult::REASON_NOT_ENOUGH_ACTIVITIES));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleSuccessfullyRequestsReport(): void
{
Carbon::setTestNow(Carbon::parse('2026-04-07 10:00:00'));
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getUuid')->willReturn('report-uuid');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_REQUESTED
&& isset($data['name'], $data['payload'], $data['requested_at'])));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->reportService->expects($this->once())
->method('getAskJiminnyGenerateReportPayload')
->willReturn(['key' => 'value']);
$this->reportService->expects($this->once())
->method('getReportFileName')
->willReturn('My Report - 7 Apr 2026');
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn(['act-1', 'act-2']);
$this->prophetClient->expects($this->once())
->method('sendRequest')
->willReturn(new \Jiminny\Component\ProphetAi\Dtos\ProphetResponseDto([]));
$this->logger->expects($this->exactly(4))
->method('info');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
Carbon::setTestNow();
}
public function testHandleCatchesGenericExceptionAndLogsError(): void
{
$this->reportService->expects($this->once())
->method('getReport')
->willThrowException(new \RuntimeException('DB error'));
$this->logger->expects($this->once())
->method('error')
->with($this->stringContains('Error'));
$job = $this->makeJob();
$reflection = new \ReflectionClass($job);
$triesProp = $reflection->getProperty('tries');
$triesProp->setAccessible(true);
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCatchesProphetExceptionAndSetsCorrectReason(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->expects($this->once())
->method('update')
->with($this->callback(fn ($data) => $data['reason'] === AutomatedReportResult::REASON_PROPHET_API_ERROR));
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willThrowException(new ProphetException('Prophet failed'));
$this->logger->expects($this->once())
->method('error');
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleCreatesReportResultBeforeActivityFetch(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$callOrder = [];
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturnCallback(function () use ($reportResult, &$callOrder) {
$callOrder[] = 'getOrCreateReportResult';
return $reportResult;
});
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturnCallback(function () use (&$callOrder) {
$callOrder[] = 'getActivityIds';
return [];
});
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
$this->assertEquals(['getOrCreateReportResult', 'getActivityIds'], $callOrder);
}
public function testHandlePassesCorrectDataToCreateReportResult(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->with(
automatedReport: $report,
data: $this->callback(fn ($data) => $data['status'] === AutomatedReportResult::STATUS_DEFAULT
&& $data['media_type'] === AutomatedReportsService::MEDIA_TYPE_PDF)
)
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->willReturn([]);
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
}
public function testHandleDispatchesNotGeneratedNotificationsWhenNoActivities(): void
{
$creator = $this->createMock(User::class);
$savedSearch = $this->createMock(\Jiminny\Models\Activity\Search::class);
$prompt = $this->createMock(\Jiminny\Models\AskAnything\AskAnythingPrompt::class);
$report = $this->makeActiveReport();
$report->method('getCreator')->willReturn($creator);
$report->method('getSavedSearch')->willReturn($savedSearch);
$report->method('getAskAnythingPrompt')->willReturn($prompt);
$report->method('getCustomName')->willReturn('My AJ Report');
$reportResult = $this->createMock(AutomatedReportResult::class);
$reportResult->method('getUuid')->willReturn('result-uuid');
$reportResult->method('update')->willReturn(true);
$this->reportService->expects($this->once())
->method('getReport')
->willReturn($report);
$this->reportService->expects($this->once())
->method('getOrCreateReportResult')
->willReturn($reportResult);
$this->activityService->expects($this->once())
->method('getActivityIdsForSavedSearch')
->willReturn([]);
$this->reportService->expects($this->once())
->method('getValidRecipientUsers')
->with($report)
->willReturn([
['email' => '[EMAIL]', 'name' => 'User One', 'timezone' => 'UTC'],
['email' => '[EMAIL]', 'name' => 'User Two', 'timezone' => 'UTC'],
]);
$this->reportService->expects($this->once())
->method('getReportPeriodName')
->with($reportResult)
->willReturn('15 - 30 Jun 2025');
$this->urlGenerator->expects($this->once())
->method('route')
->with('ai.reports.show')
->willReturn('https://example.com/ai-reports');
$dispatchedJobs = [];
$this->jobDispatcher->expects($this->exactly(2))
->method('dispatch')
->willReturnCallback(function ($dispatchedJob) use (&$dispatchedJobs) {
$dispatchedJobs[] = $dispatchedJob;
});
$job = $this->makeJob();
$job->handle($this->reportService, $this->activityService, $this->prophetClient, $this->logger, $this->urlGenerator, $this->jobDispatcher);
$this->assertCount(2, $dispatchedJobs);
$this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[0]);
$this->assertInstanceOf(SendReportNotGeneratedMailJob::class, $dispatchedJobs[1]);
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
1682
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 12:56:51] local.INFO: [automated-reports] Started {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Checking conditions {"isMonday":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Completed {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:52] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {"saved_search_id":1977,"user_id":143,"activity_count":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","recipientsCount":1} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [Send Report Not Generated Mail] Email sent {"uuid":"dcb12181-9de1-4ef0-9d45-fb4ea6fd0778","email":"[EMAIL]","recipientName":"Lukas Kovalik"} {"correlation_id":"fdf99c5e-56cc-4478-ab54-250fc09fb443","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:57:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"23c760c9-672e-4166-a16d-e55db7824744","trace_id":"683fff63-372b-4638-9ebe-d1b0d7cd47b9"}
[2026-04-22 12:57:04] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"23c760c9-672e-4166-a16d-e55db7824744","trace_id":"683fff63-372b-4638-9ebe-d1b0d7cd47b9"}
[2026-04-22 12:57:06] local.NOTICE: Monitoring start {"correlation_id":"93748590-5bcc-4b5e-82a2-9cd195faadc8","trace_id":"788e496b-9a54-46ef-a822-45932aabd9ac"}
[2026-04-22 12:57:06] local.NOTICE: Monitoring end {"correlation_id":"93748590-5bcc-4b5e-82a2-9cd195faadc8","trace_id":"788e496b-9a54-46ef-a822-45932aabd9ac"}
[2026-04-22 12:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"f7252a17-bc62-4f94-afe0-ccfd221e69b7","trace_id":"1592110b-35f8-420a-83a2-a24fb4d4a5fa"}
[2026-04-22 12:57:07] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"f7252a17-bc62-4f94-afe0-ccfd221e69b7","trace_id":"1592110b-35f8-420a-83a2-a24fb4d4a5fa"}
[2026-04-22 12:57:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:08] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:08] local.INFO: [EmailSchedule] FINISHED batch process {"host":"docker_lamp_1","processed":0} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:08] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"ebac391b-93d6-4f58-9e1b-cbd191d7a9cb","trace_id":"1178bb4a-0ff0-4592-8041-c44780544424"}
[2026-04-22 12:57:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:10] local.INFO: [EmailSchedule] STARTING batch create {"host":"docker_lamp_1"} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:10] local.INFO: [EmailSchedule] FINISHED batch create {"host":"docker_lamp_1"} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:10] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:batch:create","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"bd79807d-f781-4ab2-a534-de2e8817a0c3","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:57:11] local.INFO: [Jiminny\Jobs\Mailbox\CreateBatches] processed 2 inboxes and created 1 batches {"userId":null,"batchSize":30,"maxBatches":1000} {"correlation_id":"a4eecb2f-a606-43af-b9bd-0941ef3f15c1","trace_id":"cfcd8a63-b93b-4b43-a262-f99f08865655"}
[2026-04-22 12:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"2893f84a-555c-4fc8-a8c8-128c7e3ffc21","trace_id":"7ba30dd2-e0d1-4b47-b23d-2ab8a2944007"}
[2026-04-22 12:58:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"2893f84a-555c-4fc8-a8c8-128c7e3ffc21","trace_id":"7ba30dd2-e0d1-4b47-b23d-2ab8a2944007"}
[2026-04-22 12:58:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"2893f84a-555c-4fc8-a8c8-128c7e3ffc21","trace_id":"7ba30dd2-e0d1-4b47-b23d-2ab8a2944007"}
[2026-04-22 12:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4f659da1-6b2c-492a-bdb8-db3789f92e86","trace_id":"5f899a25-4450-47e9-ada0-a83001bd00be"}
[2026-04-22 12:58:05] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"dialers:monitor-activities","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4f659da1-6b2c-492a-bdb8-db3789f92e86","trace_id":"5f899a25-4450-47e9-ada0-a83001bd00be"}
[2026-04-22 12:58:07] local.NOTICE: Monitoring start {"correlation_id":"c45f0584-bcf3-44c9-b9a2-7f6ea9ce66c0","trace_id":"ad240a18-b6d7-43d8-a9a6-d56613965203"}
[2026-04-22 12:58:07] local.NOTICE: Monitoring end {"correlation_id":"c45f0584-bcf3-44c9-b9a2-7f6ea9ce66c0","trace_id":"ad240a18-b6d7-43d8-a9a6-d56613965203"}
[2026-04-22 12:58:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"4739d379-46ac-41e7-a396-1365033ae798","trace_id":"bb579ad2-7e23-4512-822c-2447def47d44"}
[2026-04-22 12:58:13] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"mailbox:skip-lists:refresh","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeakAfterCommandInMB":99.723} {"correlation_id":"4739d379-46ac-41e7-a396-1365033ae798","trace_id":"bb579ad2-7e23-4512-822c-2447def47d44"}
[2026-04-22 12:58:21] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"mailbox:batch:process","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [EmailSchedule] STARTING batch process {"host":"docker_lamp_1"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: Processing email batch 98408 for inbox 212 {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1354,"provider":"google"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1354,"provider":"google"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:21] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db54231f2fc510","from":"Sentry <[EMAIL]>","to":"[EMAIL]","cc":null} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db54231f2fc510","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db54231f2fc510","message_id":"<[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db53f91158f746","from":"Nikolay Yankov <[EMAIL]>","to":"\"jiminny/prophet\" <[EMAIL]>","cc":"Push <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db53f91158f746","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db53f91158f746","message_id":"<jiminny/prophet/pull/490/before/542d2c39143ba7a97af460a1362f4baa33a2b957/after/[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db53a58ac8cee4","from":"\"claude[bot]\" <[EMAIL]>","to":"\"jiminny/prophet\" <[EMAIL]>","cc":"Subscribed <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db53a58ac8cee4","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db53a58ac8cee4","message_id":"<jiminny/prophet/pull/490/[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db53a03c6854a8","from":"steliyan-g <[EMAIL]>","to":"\"jiminny/prophet\" <[EMAIL]>","cc":"Subscribed <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db53a03c6854a8","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db53a03c6854a8","message_id":"<jiminny/prophet/pull/490/[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db53948b59b0c0","from":"steliyan-g <[EMAIL]>","to":"\"jiminny/prophet\" <[EMAIL]>","cc":"Subscribed <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db53948b59b0c0","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db53948b59b0c0","message_id":"<jiminny/prophet/pull/[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db53831a6e4fc5","from":"\"sonarqubecloud[bot]\" <[EMAIL]>","to":"\"jiminny/prophet\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Review requested <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db53831a6e4fc5","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db53831a6e4fc5","message_id":"<jiminny/prophet/pull/489/[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db53521076b35f","from":"Sentry <[EMAIL]>","to":"[EMAIL]","cc":null} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db53521076b35f","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db53521076b35f","message_id":"<[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db532f4c2523b0","from":"\"claude[bot]\" <[EMAIL]>","to":"\"jiminny/prophet\" <[EMAIL]>","cc":"Lukas Kovalik <[EMAIL]>, Review requested <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Fetching token {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [SocialAccountService] Token retrieved {"socialAccountId":1500,"provider":"salesforce"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EncryptedTokenManager] Generating access token. {"mode":"legacy"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [CrmOwnerResolver] Integration owner matched as CRM Owner {"crm_provider":"salesforce","crm_owner":143,"team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsResolver] The sender email is blacklisted, skipping {"email":"[EMAIL]","inbox_id":212,"message_provider_id":"19db532f4c2523b0","team_id":1} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: [EmailImport\ParticipantsValidator] Email participants are less than 2 {"inbox_id":212,"message_provider_id":"19db532f4c2523b0","message_id":"<jiminny/prophet/pull/489/[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef288-27e5-4a85-92bb-6f3ea826f4c1"}
[2026-04-22 12:58:24] local.INFO: Processing an email from inbox batch {"batch":98408,"inbox_id":212,"email":"[EMAIL]","email_id":"19db532f07d5af3b","from":"Nikolay Yankov <[EMAIL]>","to":"\"jiminny/app\" <[EMAIL]>","cc":"Subscribed <[EMAIL]>"} {"correlation_id":"b4d5498b-7884-4951-b407-0f04e87c7643","trace_id":"64cef2...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
72014
|
|
75368
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75368
|
|
75369
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75369
|
|
75379
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75379
|
|
75380
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75380
|
|
75381
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75381
|
|
75382
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75382
|
|
75383
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75383
|
|
75384
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75384
|
|
75385
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75385
|
|
75386
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75386
|
|
75387
|
Project: faVsco.js, menu
#12011 on JY-20157-AJ-rep Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceTest
Run 'AutomatedReportsServiceTest'
Debug 'AutomatedReportsServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
10
92
69
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Carbon\Carbon;
use Illuminate\Support\Carbon as IlluminateCarbon;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Component\AskAnything\Dtos\AskAnythingPromptDto;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Contracts\Repositories\TeamRepository;
use Jiminny\Contracts\Repositories\UserRepository;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Exceptions\ModelNotFoundException;
use Illuminate\Support\Collection;
use Jiminny\Models\AskAnything\AskAnythingPrompt;
use Jiminny\Models\AskAnything\AskAnythingPromptTarget;
use Jiminny\Models\Activity\Search;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Models\Feature\FeatureEnum;
use Jiminny\Models\Group;
use Jiminny\Models\Team;
use Jiminny\Models\User;
use Jiminny\Repositories\AskAnythingRepository;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AutomatedReportsServiceTest extends TestCase
{
private AutomatedReportsService $service;
protected function setUp(): void
{
parent::setUp();
// Create a real instance of the service without calling the constructor
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$this->service = $reflection->newInstanceWithoutConstructor();
// Manually set the dependencies using reflection
$dependencies = [
'teamRepository' => TeamRepository::class,
'groupRepository' => GroupRepository::class,
'userRepository' => UserRepository::class,
'stageRepository' => StageRepository::class,
'dealStagesService' => DealStagesService::class,
'recipientsService' => RecipientsService::class,
'automatedReportsRepository' => AutomatedReportsRepository::class,
'webhookService' => Webhook::class,
'dispatcher' => Dispatcher::class,
'activityTypeService' => ActivityTypeService::class,
'playbookCategoryRepository' => PlaybookCategoryRepository::class,
'askAnythingPromptService' => AskAnythingPromptService::class,
'activitySearchRepository' => SearchRepository::class,
'askAnythingRepository' => AskAnythingRepository::class,
];
foreach ($dependencies as $propertyName => $class) {
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
$property->setValue($this->service, $this->createMock($class));
}
}
protected function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
private function getService(
$mockUserRepository = null,
$mockStageRepository = null,
$mockTeamRepository = null,
): AutomatedReportsService {
return new AutomatedReportsService(
($mockTeamRepository ?? $this->createMock(TeamRepository::class)),
$this->createMock(GroupRepository::class),
($mockUserRepository ?? $this->createMock(UserRepository::class)),
($mockStageRepository ?? $this->createMock(StageRepository::class)),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$this->createMock(Dispatcher::class),
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
#[DataProvider('transformMediaTypesDataProvider')]
public function testTransformMediaTypes(array $mediaTypes, array $expected): void
{
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$reflection = new \ReflectionClass(AutomatedReportsService::class);
$method = $reflection->getMethod('transformMediaTypes');
$result = $method->invoke($this->service, $report);
$this->assertEquals($expected, $result);
}
public function testGetMediaTypeFieldDataWithoutReport(): void
{
$result = $this->service->getMediaTypeFieldData(null);
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEmpty($result['value']);
$this->assertEquals('media_types', $result['id']);
}
public function testGetMediaTypeFieldDataWithReport(): void
{
$mediaTypes = ['pdf', 'podcast'];
$report = new AutomatedReport(['media_types' => $mediaTypes]);
$result = $this->service->getMediaTypeFieldData($report);
$expectedValue = [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
];
$this->assertIsArray($result);
$this->assertArrayHasKey('value', $result);
$this->assertEquals($expectedValue, $result['value']);
}
public static function transformMediaTypesDataProvider(): array
{
return [
'empty array' => [
'mediaTypes' => [],
'expected' => [],
],
'pdf only' => [
'mediaTypes' => ['pdf'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
],
],
'podcast only' => [
'mediaTypes' => ['podcast'],
'expected' => [
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'both pdf and podcast' => [
'mediaTypes' => ['pdf', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
'with invalid type' => [
'mediaTypes' => ['pdf', 'invalid', 'podcast'],
'expected' => [
['id' => 'pdf', 'name' => 'PDF'],
['id' => 'podcast', 'name' => 'Podcast'],
],
],
];
}
#[DataProvider('hasCallTypeConferenceDataProvider')]
public function testHasCallTypeConference(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeConference($report);
$this->assertEquals($expected, $result);
}
#[DataProvider('hasCallTypeDialerDataProvider')]
public function testHasCallTypeDialer(array $callTypes, bool $expected): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getCallTypes')->willReturn($callTypes);
$result = $this->service->hasCallTypeDialer($report);
$this->assertEquals($expected, $result);
}
public static function hasCallTypeConferenceDataProvider(): array
{
return [
'has conference' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have conference' => [
'callTypes' => ['dialer', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public static function hasCallTypeDialerDataProvider(): array
{
return [
'has dialer' => [
'callTypes' => ['conference', 'dialer'],
'expected' => true,
],
'does not have dialer' => [
'callTypes' => ['conference', 'other'],
'expected' => false,
],
'empty call types' => [
'callTypes' => [],
'expected' => false,
],
];
}
public function testTransformReportResultsWithEmptyCollection(): void
{
$emptyCollection = new Collection([]);
$result = $this->service->transformReportResults($emptyCollection);
$this->assertIsArray($result);
$this->assertEmpty($result);
}
public function testTransformReportResultsStructure(): void
{
// Create a mock AutomatedReportResult with minimal setup to test structure
$mockReportResult = $this->createMockReportResult();
$collection = new Collection([$mockReportResult]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(1, $result);
$transformedResult = $result[0];
// Verify all expected keys are present
$expectedKeys = [
'id', 'name', 'frequency', 'recipients',
'report_type', 'media_type', 'downloadUrl', 'viewUrl', 'generated_at',
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $transformedResult);
}
// Verify structure of nested arrays
$this->assertIsArray($transformedResult['frequency']);
$this->assertArrayHasKey('id', $transformedResult['frequency']);
$this->assertArrayHasKey('name', $transformedResult['frequency']);
$this->assertIsArray($transformedResult['report_type']);
$this->assertArrayHasKey('id', $transformedResult['report_type']);
$this->assertArrayHasKey('name', $transformedResult['report_type']);
$this->assertIsArray($transformedResult['recipients']);
// Verify TODO fields are null as expected
$this->assertEquals(AutomatedReportsService::MEDIA_TYPE_PODCAST, $transformedResult['media_type']);
$this->assertEquals(route('ai-reports.audio.download', ['uuid' => 'test-uuid']), $transformedResult['downloadUrl']);
$this->assertEquals(route('ai-reports.audio.view', ['uuid' => 'test-uuid']), $transformedResult['viewUrl']);
}
public function testTransformReportResultsWithMultipleResults(): void
{
$mockReportResult1 = $this->createMockReportResult('result-uuid-1', 'exec_summary');
$mockReportResult2 = $this->createMockReportResult('result-uuid-2', 'coaching_profiles');
$collection = new Collection([$mockReportResult1, $mockReportResult2]);
$result = $this->service->transformReportResults($collection);
$this->assertIsArray($result);
$this->assertCount(2, $result);
// Verify different UUIDs
$this->assertEquals('result-uuid-1', $result[0]['id']);
$this->assertEquals('result-uuid-2', $result[1]['id']);
// Verify both results have the expected structure
foreach ($result as $transformedResult) {
$this->assertArrayHasKey('id', $transformedResult);
$this->assertArrayHasKey('name', $transformedResult);
$this->assertArrayHasKey('frequency', $transformedResult);
$this->assertArrayHasKey('recipients', $transformedResult);
$this->assertArrayHasKey('report_type', $transformedResult);
}
}
#[DataProvider('isUserRecipientOfReportDataProvider')]
public function testIsUserRecipientOfReport(int $userId, array $recipients, bool $expected): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn(null);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertEquals($expected, $result);
}
#[DataProvider('isUserRecipientOfAskJiminnyReportDataProvider')]
public function testIsUserRecipientOfAskJiminnyReportViaGroup(
int $userId,
?int $groupId,
array $recipients,
array $reportGroups,
bool $expected,
): void {
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn($userId);
$mockUser->method('getGroupId')->willReturn($groupId);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$mockReport->method('isAskJiminnyReport')->willReturn(true);
$mockReport->method('getGroups')->willReturn($reportGroups);
$this->assertSame($expected, $this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public function testIsUserRecipientOfNonAskJiminnyReportIgnoresGroups(): void
{
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
$mockUser->method('getGroupId')->willReturn(5);
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => []]);
$mockReport->method('isAskJiminnyReport')->willReturn(false);
$mockReport->method('getGroups')->willReturn([5]);
$this->assertFalse($this->service->isUserRecipientOfReport($mockUser, $mockReport));
}
public static function isUserRecipientOfAskJiminnyReportDataProvider(): array
{
return [
'group member - ask jiminny' => [
'userId' => 123,
'groupId' => 7,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => true,
],
'group mismatch - ask jiminny' => [
'userId' => 123,
'groupId' => 9,
'recipients' => ['users' => []],
'reportGroups' => [7, 8],
'expected' => false,
],
'user with no group - ask jiminny' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => []],
'reportGroups' => [7],
'expected' => false,
],
'recipient users take precedence over group' => [
'userId' => 123,
'groupId' => null,
'recipients' => ['users' => [123]],
'reportGroups' => [],
'expected' => true,
],
];
}
public function testIsUserRecipientOfReportWithEmptyRecipients(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with no recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public function testIsUserRecipientOfReportWithNoUsersKey(): void
{
// Create mock User
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getId')->willReturn(123);
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [456, 789]]);
$result = $this->service->isUserRecipientOfReport($mockUser, $mockReport);
$this->assertFalse($result);
}
public static function isUserRecipientOfReportDataProvider(): array
{
return [
'user is recipient - single user' => [
'userId' => 123,
'recipients' => ['users' => [123]],
'expected' => true,
],
'user is recipient - multiple users' => [
'userId' => 456,
'recipients' => ['users' => [123, 456, 789]],
'expected' => true,
],
'user is not recipient - single user' => [
'userId' => 999,
'recipients' => ['users' => [123]],
'expected' => false,
],
'user is not recipient - multiple users' => [
'userId' => 999,
'recipients' => ['users' => [123, 456, 789]],
'expected' => false,
],
'user is recipient - string IDs converted to int' => [
'userId' => 123,
'recipients' => ['users' => ['123', '456']],
'expected' => true,
],
'user is not recipient - string IDs converted to int' => [
'userId' => 999,
'recipients' => ['users' => ['123', '456']],
'expected' => false,
],
'empty users array' => [
'userId' => 123,
'recipients' => ['users' => []],
'expected' => false,
],
];
}
private function createMockReportResult(string $uuid = 'test-uuid', string $reportType = 'exec_summary'): AutomatedReportResult
{
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getFrequency')->willReturn('weekly');
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2]]);
$mockReport->method('getGroups')->willReturn([10, 20]);
$mockReport->method('getType')->willReturn($reportType);
// Create mock Team
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock Group
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn('group-uuid-10');
$mockGroup->method('getName')->willReturn('Test Team');
$mockQueryBuilder = Mockery::mock();
$mockQueryBuilder->shouldReceive('where')->andReturnSelf();
$mockQueryBuilder->shouldReceive('first')->andReturn($mockGroup);
$dataRelation = Mockery::mock(HasMany::class);
$dataRelation->shouldReceive('where')->andReturn($mockQueryBuilder);
$dataRelation->shouldReceive('get')->andReturn(
new \Illuminate\Database\Eloquent\Collection([$mockGroup])
);
$mockTeam->method('groups')->willReturn($dataRelation);
$mockReport->method('getTeam')->willReturn($mockTeam);
// Create mock AutomatedReportResult
$mockReportResult = $this->createMock(AutomatedReportResult::class);
$mockReportResult->method('getUuid')->willReturn($uuid);
$mockReportResult->method('getGeneratedAt')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15T10:30:00Z')
);
$mockReportResult->method('getReport')->willReturn($mockReport);
// Mock methods used in getReportFileName
$mockReportResult->method('getReportType')->willReturn($reportType);
$mockReportResult->method('getFromDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-08')
);
$mockReportResult->method('getToDate')->willReturn(
\Illuminate\Support\Carbon::parse('2024-01-15')
);
$mockReportResult->method('getGroups')->willReturn([10]);
$mockReportResult->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PODCAST);
return $mockReportResult;
}
#[DataProvider('getUsersUuidsDataProvider')]
public function testGetUsersUuids(array $recipients, array $mockUsers, array $expectedUuids): void
{
// Create mock UserRepository
$mockUserRepository = $this->createMock(UserRepository::class);
// Configure the mock to return specific users for specific IDs using a callback
$mockUserRepository->method('find')
->willReturnCallback(function ($userId) use ($mockUsers) {
if (! isset($mockUsers[$userId])) {
return null;
}
$userUuid = $mockUsers[$userId]['uuid'] ?? null;
if ($userUuid === null) {
return null;
}
$mockUser = $this->createMock(\Jiminny\Models\User::class);
$mockUser->method('getUuid')->willReturn((string) $userUuid);
return $mockUser;
});
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn($recipients);
$result = $automatedReportsService->getUsersUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetUsersUuidsWithEmptyRecipients(): void
{
// Create mock AutomatedReport with empty recipients
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn([]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNoUsersKey(): void
{
// Create mock AutomatedReport with recipients but no 'users' key
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['other_key' => [1, 2, 3]]);
$result = $this->service->getUsersUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetUsersUuidsWithNonExistentUsers(): void
{
// Create mock UserRepository that returns null for all users
$mockUserRepository = $this->createMock(UserRepository::class);
$mockUserRepository->method('find')->willReturn(null);
// Create service with mocked UserRepository
$automatedReportsService = $this->getService(mockUserRepository: $mockUserRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getRecipients')->willReturn(['users' => [1, 2, 3]]);
$result = $automatedReportsService->getUsersUuids($mockReport);
// Should return array with null values for non-existent users
$this->assertEquals([], $result);
}
public static function getUsersUuidsDataProvider(): array
{
return [
'single user found' => [
'recipients' => ['users' => [123]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
],
'expectedUuids' => ['user-uuid-123'],
],
'multiple users found' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
456 => ['id' => 456, 'uuid' => 'user-uuid-456'],
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-456', 'user-uuid-789'],
],
'mixed found and not found users' => [
'recipients' => ['users' => [123, 456, 789]],
'mockUsers' => [
123 => ['id' => 123, 'uuid' => 'user-uuid-123'],
// 456 not found in DB
789 => ['id' => 789, 'uuid' => 'user-uuid-789'],
],
'expectedUuids' => ['user-uuid-123', 'user-uuid-789'], // Updated to reflect that nulls are filtered out
],
'empty users array' => [
'recipients' => ['users' => []],
'mockUsers' => [],
'expectedUuids' => [],
],
'all users not found' => [
'recipients' => ['users' => [123, 456]],
'mockUsers' => [], // No users found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getCurrentDealStagesUuidsDataProvider')]
public function testGetCurrentDealStagesUuids(array $currentDealStages, array $mockStages, array $expectedUuids): void
{
// Create mock StageRepository
$mockStageRepository = $this->createMock(StageRepository::class);
// Configure the mock to return specific stages for specific IDs using a callback
$mockStageRepository->method('find')
->willReturnCallback(function ($stageId) use ($mockStages) {
if (! isset($mockStages[$stageId])) {
return null;
}
$stageUuid = $mockStages[$stageId]['uuid'] ?? null;
if ($stageUuid === null) {
return null;
}
$mockStage = $this->createMock(\Jiminny\Models\Stage::class);
$mockStage->method('getUuid')->willReturn((string) $stageUuid);
return $mockStage;
});
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn($currentDealStages);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
$this->assertEquals($expectedUuids, $result);
}
public function testGetCurrentDealStagesUuidsWithEmptyStages(): void
{
// Create mock AutomatedReport with empty current deal stages
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([]);
$result = $this->service->getCurrentDealStagesUuids($mockReport);
$this->assertEquals([], $result);
}
public function testGetCurrentDealStagesUuidsWithNonExistentStages(): void
{
// Create mock StageRepository that returns null for all stages
$mockStageRepository = $this->createMock(StageRepository::class);
$mockStageRepository->method('find')->willReturn(null);
// Create service with mocked StageRepository
$automatedReportsService = $this->getService(mockStageRepository: $mockStageRepository);
// Create mock AutomatedReport
$mockReport = $this->createMock(AutomatedReport::class);
$mockReport->method('getCurrentDealStages')->willReturn([1, 2, 3]);
$result = $automatedReportsService->getCurrentDealStagesUuids($mockReport);
// Should return array with null values for non-existent stages
$this->assertEquals([], $result);
}
public static function getCurrentDealStagesUuidsDataProvider(): array
{
return [
'single stage found' => [
'currentDealStages' => [10],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
],
'expectedUuids' => ['stage-uuid-10'],
],
'multiple stages found' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
20 => ['id' => 20, 'uuid' => 'stage-uuid-20'],
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-20', 'stage-uuid-30'],
],
'mixed found and not found stages' => [
'currentDealStages' => [10, 20, 30],
'mockStages' => [
10 => ['id' => 10, 'uuid' => 'stage-uuid-10'],
// 20 not found in DB
30 => ['id' => 30, 'uuid' => 'stage-uuid-30'],
],
'expectedUuids' => ['stage-uuid-10', 'stage-uuid-30'], // Updated to reflect that nulls are filtered out
],
'empty stages array' => [
'currentDealStages' => [],
'mockStages' => [],
'expectedUuids' => [],
],
'all stages not found' => [
'currentDealStages' => [10, 20],
'mockStages' => [], // No stages found
'expectedUuids' => [], // Updated to reflect that nulls are filtered out
],
];
}
#[DataProvider('getTeamGroupsDataProvider')]
public function testGetTeamGroups(string $teamUuid, ?array $mockTeamData, array $mockGroups, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
if ($mockTeamData === null) {
// Team not found
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn(null);
} else {
// Team found - create mock team with groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create mock groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
// Create mock Group objects
$groupObjects = [];
foreach ($mockGroups as $groupData) {
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getUuid')->willReturn($groupData['id']);
$mockGroup->method('getName')->willReturn($groupData['name']);
$groupObjects[] = $mockGroup;
}
// Mock the groups collection to return our mock groups
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator($groupObjects));
// Mock the groups() relation
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
$mockTeamRepository->method('idOrUuid')
->with($teamUuid)
->willReturn($mockTeam);
}
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups($teamUuid);
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamGroupsWithNonExistentTeam(): void
{
// Create mock TeamRepository that returns null (team not found)
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn(null);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('non-existent-team-uuid');
$this->assertEquals([], $result);
}
public function testGetTeamGroupsWithEmptyGroups(): void
{
// Create mock team with no groups
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
// Create empty groups collection
$mockGroupsCollection = $this->createMock(\Illuminate\Database\Eloquent\Collection::class);
$mockGroupsCollection->method('getIterator')->willReturn(new \ArrayIterator([]));
$mockGroupsRelation = $this->createMock(\Illuminate\Database\Eloquent\Relations\HasMany::class);
$mockGroupsRelation->method('get')->willReturn($mockGroupsCollection);
$mockTeam->method('groups')->willReturn($mockGroupsRelation);
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('idOrUuid')->willReturn($mockTeam);
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeamGroups('team-with-no-groups');
$this->assertEquals([], $result);
}
public static function getTeamGroupsDataProvider(): array
{
return [
'team with single group' => [
'teamUuid' => 'team-uuid-123',
'mockTeamData' => ['id' => 'team-uuid-123', 'name' => 'Test Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
],
],
'team with multiple groups' => [
'teamUuid' => 'team-uuid-456',
'mockTeamData' => ['id' => 'team-uuid-456', 'name' => 'Another Team'],
'mockGroups' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
'expectedResult' => [
['id' => 'group-uuid-1', 'name' => 'Sales Team'],
['id' => 'group-uuid-2', 'name' => 'Marketing Team'],
['id' => 'group-uuid-3', 'name' => 'Support Team'],
],
],
'team not found' => [
'teamUuid' => 'non-existent-uuid',
'mockTeamData' => null,
'mockGroups' => [],
'expectedResult' => [],
],
'team with no groups' => [
'teamUuid' => 'team-uuid-empty',
'mockTeamData' => ['id' => 'team-uuid-empty', 'name' => 'Empty Team'],
'mockGroups' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('getTeamsDataProvider')]
public function testGetTeams(array $mockTeams, array $expectedResult): void
{
// Create mock TeamRepository
$mockTeamRepository = $this->createMock(TeamRepository::class);
// Create mock Team objects
$teamObjects = [];
foreach ($mockTeams as $teamData) {
$mockTeam = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam->method('getUuid')->willReturn($teamData['id']);
$mockTeam->method('getName')->willReturn($teamData['name']);
$mockTeam->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn($teamData['hasAutomatedReports']);
$teamObjects[] = $mockTeam;
}
// Mock the repository to return a Collection (not array)
$mockTeamRepository->method('getTeamsForKiosk')
->with('active')
->willReturn(new Collection($teamObjects));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals($expectedResult, $result);
}
public function testGetTeamsWithNoTeams(): void
{
// Create mock TeamRepository that returns empty Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public function testGetTeamsWithAllTeamsWithoutFeature(): void
{
// Create mock teams without AUTOMATED_REPORTS feature
$mockTeam1 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam1->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
$mockTeam2 = $this->createMock(\Jiminny\Models\Team::class);
$mockTeam2->method('hasFeature')
->with(\Jiminny\Models\Feature\FeatureEnum::AUTOMATED_REPORTS)
->willReturn(false);
// Create mock TeamRepository that returns Collection
$mockTeamRepository = $this->createMock(TeamRepository::class);
$mockTeamRepository->method('getTeamsForKiosk')->willReturn(new Collection([$mockTeam1, $mockTeam2]));
// Create service with mocked TeamRepository
$automatedReportsService = $this->getService(mockTeamRepository: $mockTeamRepository);
$result = $automatedReportsService->getTeams();
$this->assertEquals([], $result);
}
public static function getTeamsDataProvider(): array
{
return [
'single team with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
],
],
'multiple teams with feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-2', 'name' => 'Marketing Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'mixed teams - some with feature, some without' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => true,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-3',
'name' => 'Support Team',
'hasAutomatedReports' => true,
],
],
'expectedResult' => [
['id' => 'team-uuid-1', 'name' => 'Sales Team'],
['id' => 'team-uuid-3', 'name' => 'Support Team'],
],
],
'all teams without feature' => [
'mockTeams' => [
[
'id' => 'team-uuid-1',
'name' => 'Sales Team',
'hasAutomatedReports' => false,
],
[
'id' => 'team-uuid-2',
'name' => 'Marketing Team',
'hasAutomatedReports' => false,
],
],
'expectedResult' => [],
],
'empty teams array' => [
'mockTeams' => [],
'expectedResult' => [],
],
];
}
#[DataProvider('deleteS3FilesDataProvider')]
public function testDeleteS3Files(
string $mediaType,
array $expectedFileExtensions,
array $existingFiles,
string $pathSuffix,
int $expectedDeletes
): void {
// Arrange
$teamUuid = 'team-uuid-123';
$reportUuid = 'report-uuid-456';
$basePath = sprintf('%s/reports/%s', $teamUuid, $reportUuid);
$team = Mockery::mock(Team::class);
$team->allows('getUuid')->andReturn($teamUuid);
$report = Mockery::mock(AutomatedReport::class);
$report->allows('getTeam')->andReturn($team);
$result = Mockery::mock(AutomatedReportResult::class);
$result->allows('getReport')->andReturn($report);
$result->allows('getUuid')->andReturn($reportUuid);
$result->allows('getMediaType')->andReturn($mediaType);
Storage::fake();
Log::shouldReceive('info')->times($expectedDeletes);
foreach ($existingFiles as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
Storage::put($filePath, 'dummy content');
}
// Act
$this->service->deleteS3Files($result);
// Assert
foreach ($expectedFileExtensions as $extension) {
$filePath = $basePath . $pathSuffix . '.' . $extension;
if (in_array($extension, $existingFiles, true)) {
Storage::assertMissing($filePath);
} else {
// To be sure no unexpected files were created and deleted
Storage::assertMissing($filePath);
}
}
}
public static function deleteS3FilesDataProvider(): array
{
return [
'PDF report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'MD', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 3,
],
'PDF report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => ['html', 'pdf'],
'pathSuffix' => '',
'expectedDeletes' => 2,
],
'PDF report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PDF,
'expectedFileExtensions' => ['html', 'MD', 'pdf'],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
'Podcast report, all files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['json', 'mp3', 'ssml'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 3,
],
'Podcast report, some files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => ['mp3'],
'pathSuffix' => '_podcast',
'expectedDeletes' => 1,
],
'Podcast report, no files exist' => [
'mediaType' => AutomatedReportsService::MEDIA_TYPE_PODCAST,
'expectedFileExtensions' => ['json', 'mp3', 'ssml'],
'existingFiles' => [],
'pathSuffix' => '_podcast',
'expectedDeletes' => 0,
],
'Other media type, should do nothing' => [
'mediaType' => 'some_other_type',
'expectedFileExtensions' => [],
'existingFiles' => [],
'pathSuffix' => '',
'expectedDeletes' => 0,
],
];
}
public function testDeleteReportsResultsInRetentionPeriodWithLogging(): void
{
// Create mocks for the test
$automatedReportsService = Mockery::mock(AutomatedReportsService::class);
$team = Mockery::mock(Team::class);
$team->shouldReceive('getId')->andReturn(123);
$from = now()->subDays(30);
$to = now();
$source = 'test-source';
// Expect the method to be called with specific parameters
$automatedReportsService->shouldReceive('deleteReportsResultsInRetentionPeriodWithLogging')
->once()
->with(
$team,
Mockery::on(function ($arg) use ($from) {
return $arg->timestamp === $from->timestamp;
}),
Mockery::on(function ($arg) use ($to) {
return $arg->timestamp === $to->timestamp;
}),
$source
)
->andReturn(5);
// Call the method and verify the result
$result = $automatedReportsService->deleteReportsResultsInRetentionPeriodWithLogging(
$team,
$from,
$to,
$source
);
$this->assertEquals(5, $result);
}
#[DataProvider('sanitizeFileNameDataProvider')]
public function testSanitizeFileName(string $input, string $expected): void
{
$result = $this->service->sanitizeFileName($input);
$this->assertEquals($expected, $result);
}
public static function sanitizeFileNameDataProvider(): array
{
return [
'no special characters' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team',
],
'forward slash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND/IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'backslash in team name' => [
'input' => 'Exec Summary - Sep 2025 - ND\IRV',
'expected' => 'Exec Summary - Sep 2025 - ND-IRV',
],
'multiple forward slashes' => [
'input' => 'Report - Team A/B/C',
'expected' => 'Report - Team A-B-C',
],
'multiple backslashes' => [
'input' => 'Report - Team A\B\C',
'expected' => 'Report - Team A-B-C',
],
'mixed slashes and backslashes' => [
'input' => 'Report - Team A/B\C',
'expected' => 'Report - Team A-B-C',
],
'complex team name with slashes' => [
'input' => 'Exec Summary - Sep 2025 - Business Development Team - ND/IRV, Net Driven - Acquisition (Sales)',
'expected' => 'Exec Summary - Sep 2025 - Business Development Team - ND-IRV, Net Driven - Acquisition (Sales)',
],
'only slashes' => [
'input' => '//\\\\',
'expected' => '----',
],
'empty string' => [
'input' => '',
'expected' => '',
],
'slash at start' => [
'input' => '/Report Name',
'expected' => '-Report Name',
],
'slash at end' => [
'input' => 'Report Name/',
'expected' => 'Report Name-',
],
];
}
public function testGetReportFileNameSanitizesOutput(): void
{
// Create mock GroupRepository
$mockGroupRepository = $this->createMock(GroupRepository::class);
// Create mock Group with slash in name
$mockGroup = $this->createMock(\Jiminny\Models\Group::class);
$mockGroup->method('getName')->willReturn('ND/IRV, Net Driven - Acquisition (Sales)');
$mockGroupRepository->method('find')->willReturn($mockGroup);
// Create service with mocked GroupRepository
$service = new AutomatedReportsService(
$this-...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75387
|
|
75639
|
2 files committed
JY-20157 fix failing tests
text/ 2 files committed
JY-20157 fix failing tests
text/html
text/html
text/html
Edit Commit Message…
Project: faVsco.js, menu
#12011 on JY-20157-AJ-report-not-send-notification, menu
Start Listening for PHP Debug Connections
AutomatedReportsServiceReportGen…tionTest
Run 'AutomatedReportsServiceReportGenerationTest'
Debug 'AutomatedReportsServiceReportGenerationTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
46
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Tests\Unit\Services\Kiosk\AutomatedReports;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Support\Carbon;
use InvalidArgumentException;
use Jiminny\Component\UrlGenerator\Webhook;
use Jiminny\Contracts\Repositories\PlaybookCategoryRepository;
use Jiminny\Models\AutomatedReport;
use Jiminny\Models\AutomatedReportResult;
use Jiminny\Repositories\AutomatedReportsRepository;
use Jiminny\Repositories\GroupRepository;
use Jiminny\Repositories\StageRepository;
use Jiminny\Repositories\TeamRepository;
use Jiminny\Repositories\UserRepository;
use Jiminny\Services\Kiosk\AutomatedReports\ActivityTypeService;
use Jiminny\Services\Kiosk\AutomatedReports\AutomatedReportsService;
use Jiminny\Services\Kiosk\AutomatedReports\DealStagesService;
use Jiminny\Services\Kiosk\AutomatedReports\RecipientsService;
use Jiminny\Component\AskAnything\AskAnythingPromptService;
use Jiminny\Repositories\SearchRepository;
use Jiminny\Repositories\AskAnythingRepository;
use Tests\TestCase;
use Tests\Unit\Traits\TestPrivateMethod;
class AutomatedReportsServiceReportGenerationTest extends TestCase
{
use TestPrivateMethod;
private AutomatedReportsService $service;
private Webhook $webhookService;
private AutomatedReportsRepository $automatedReportsRepository;
private TeamRepository $teamRepository;
protected function setUp(): void
{
parent::setUp();
// Create mocks for dependencies
$this->teamRepository = $this->createMock(TeamRepository::class);
$teamRepository = $this->teamRepository;
$groupRepository = $this->createMock(GroupRepository::class);
$userRepository = $this->createMock(UserRepository::class);
$stageRepository = $this->createMock(StageRepository::class);
$dealStagesService = $this->createMock(DealStagesService::class);
$recipientsService = $this->createMock(RecipientsService::class);
$this->automatedReportsRepository = $this->createMock(AutomatedReportsRepository::class);
$this->webhookService = $this->createMock(Webhook::class);
$dispatcher = $this->createMock(BusDispatcher::class);
$activityTypeService = $this->createMock(ActivityTypeService::class);
$playbookCategoryRepository = $this->createMock(PlaybookCategoryRepository::class);
// Create service instance with mocked dependencies
$this->service = new AutomatedReportsService(
$teamRepository,
$groupRepository,
$userRepository,
$stageRepository,
$dealStagesService,
$recipientsService,
$this->automatedReportsRepository,
$this->webhookService,
$dispatcher,
$activityTypeService,
$playbookCategoryRepository,
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
/**
* Test getGenerateReportPayload method with one-off frequency
*/
public function testGetGenerateReportPayloadWithOneOffFrequency(): void
{
$reportUuid = 'report-uuid';
$resultUuid = 'result-uuid';
$callbackUrl = 'https://example.com/webhook/reports/ready';
// Mock report
$report = $this->createMock(AutomatedReport::class);
$report->method('getTeamId')->willReturn(1);
$report->method('getGroups')->willReturn([101, 102]);
$report->method('getType')->willReturn('exec_summary');
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_ONE_OFF);
$report->method('getFrom')->willReturn(Carbon::parse('2023-01-01'));
$report->method('getTo')->willReturn(Carbon::parse('2023-01-31'));
$report->method('getDealAtCallStages')->willReturn([201, 202]);
$report->method('getCurrentDealStages')->willReturn([301, 302]);
$report->method('getDealValueMin')->willReturn(1000);
$report->method('getDealValueMax')->willReturn(5000);
$report->method('getCallTypes')->willReturn(['conference', 'dialer']);
$report->method('getCallDurationMin')->willReturn(600);
$report->method('getCallDurationMax')->willReturn(1800);
$report->method('getAdditionalPromptInput')->willReturn('Focus on sales performance');
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_ONE_OFF);
$report->method('getPlaybookCategories')->willReturn(['category1', 'category2']);
$report->method('getCustomName')->willReturn('My Custom Report');
$result = $this->createMock(AutomatedReportResult::class);
$result->method('getFromDate')->willReturn(Carbon::parse('2023-01-01'));
$result->method('getToDate')->willReturn(Carbon::parse('2023-01-31'));
$result->method('getReport')->willReturn($report);
$this->automatedReportsRepository->method('findResultByUuid')->willReturn($result);
// Set up webhook service to return callback URL
$this->webhookService->expects($this->once())
->method('route')
->with('jiminny.webhook.reports.ready')
->willReturn($callbackUrl);
// Call the method
$result = $this->service->getGenerateReportPayload($report, $resultUuid);
// Verify result
$this->assertEquals(1, $result['team_id']);
$this->assertEquals([101, 102], $result['group_ids']);
$this->assertEquals('exec_summary', $result['report_type']);
$this->assertEquals('2023-01-01T00:00:00+00:00', $result['from_date']);
$this->assertEquals('2023-01-31T23:59:59+00:00', $result['to_date']);
$this->assertEquals([201, 202], $result['call_deal_stage']);
$this->assertEquals([301, 302], $result['current_deal_stage']);
$this->assertEquals(1000, $result['deal_min_value']);
$this->assertEquals(5000, $result['deal_max_value']);
$this->assertEquals(['conference', 'dialer'], $result['call_types']);
$this->assertEquals(600, $result['call_duration_min_seconds']);
$this->assertEquals(1800, $result['call_duration_max_seconds']);
$this->assertEquals('Focus on sales performance', $result['special_requirements']);
$this->assertEquals($resultUuid, $result['request_id']);
$this->assertEquals($callbackUrl, $result['callback_url']);
$this->assertEquals('1 - 31 Jan 2023', $result['report_period']);
$this->assertEquals(['category1', 'category2'], $result['playbook_categories']);
$this->assertEquals('My Custom Report', $result['custom_name']);
}
/**
* Test calculateFromAndToDate method with one-off frequency
*/
public function testCalculateFromAndToDateWithOneOffFrequency(): void
{
// Mock report
$report = $this->createMock(AutomatedReport::class);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_ONE_OFF);
$report->method('getFrom')->willReturn(Carbon::parse('2023-01-01'));
$report->method('getTo')->willReturn(Carbon::parse('2023-01-31'));
// Access private method using reflection
$result = self::invokePrivateMethod('calculateFromAndToDate', $this->service, [$report]);
// Verify result
$this->assertInstanceOf(Carbon::class, $result['fromDate']);
$this->assertInstanceOf(Carbon::class, $result['toDate']);
$this->assertEquals('2023-01-01', $result['fromDate']->format('Y-m-d'));
$this->assertEquals('2023-01-31', $result['toDate']->format('Y-m-d'));
}
/**
* Test calculateFromAndToDate method with weekly frequency
*/
public function testCalculateFromAndToDateWithWeeklyFrequency(): void
{
// Mock report
$report = $this->createMock(AutomatedReport::class);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_WEEKLY);
// Freeze time for testing
Carbon::setTestNow(Carbon::parse('2023-02-15 14:30:00'));
// Access private method using reflection
$result = self::invokePrivateMethod('calculateFromAndToDate', $this->service, [$report]);
// Verify result
$this->assertInstanceOf(Carbon::class, $result['fromDate']);
$this->assertInstanceOf(Carbon::class, $result['toDate']);
$this->assertEquals('2023-02-05 00:00:00', $result['fromDate']->format('Y-m-d H:i:s'));
$this->assertEquals('2023-02-11 23:59:59', $result['toDate']->format('Y-m-d H:i:s'));
// Reset frozen time
Carbon::setTestNow();
}
/**
* Test calculateFromAndToDate method with monthly frequency
*/
public function testCalculateFromAndToDateWithMonthlyFrequency(): void
{
// Mock report
$report = $this->createMock(AutomatedReport::class);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
// Freeze time for testing
Carbon::setTestNow(Carbon::parse('2023-02-15 14:30:00'));
// Access private method using reflection
$result = self::invokePrivateMethod('calculateFromAndToDate', $this->service, [$report]);
// Verify result
$this->assertInstanceOf(Carbon::class, $result['fromDate']);
$this->assertInstanceOf(Carbon::class, $result['toDate']);
$this->assertEquals('2023-01-01 00:00:00', $result['fromDate']->format('Y-m-d H:i:s'));
$this->assertEquals('2023-01-31 23:59:59', $result['toDate']->format('Y-m-d H:i:s'));
// Reset frozen time
Carbon::setTestNow();
}
/**
* Test calculateFromAndToDate method with quarterly frequency
*/
public function testCalculateFromAndToDateWithQuarterlyFrequency(): void
{
// Mock report
$report = $this->createMock(AutomatedReport::class);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_QUARTERLY);
// Freeze time for testing
Carbon::setTestNow(Carbon::parse('2023-02-15 14:30:00'));
// Access private method using reflection
$result = self::invokePrivateMethod('calculateFromAndToDate', $this->service, [$report]);
// Verify result
$this->assertInstanceOf(Carbon::class, $result['fromDate']);
$this->assertInstanceOf(Carbon::class, $result['toDate']);
$this->assertEquals('2022-10-01 00:00:00', $result['fromDate']->format('Y-m-d H:i:s'));
$this->assertEquals('2022-12-31 23:59:59', $result['toDate']->format('Y-m-d H:i:s'));
// Reset frozen time
Carbon::setTestNow();
}
/**
* Test calculateFromAndToDate method with invalid frequency
*/
public function testCalculateFromAndToDateWithInvalidFrequency(): void
{
// Mock report
$report = $this->createMock(AutomatedReport::class);
$report->method('getFrequency')->willReturn('invalid_frequency');
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Unsupported frequency: invalid_frequency');
// Access private method using reflection
self::invokePrivateMethod('calculateFromAndToDate', $this->service, [$report]);
}
/**
* Test getCallbackUrl method
*/
public function testGetCallbackUrl(): void
{
$callbackUrl = 'https://example.com/webhook/reports/ready';
// Set up webhook service to return callback URL
$this->webhookService->expects($this->once())
->method('route')
->with('jiminny.webhook.reports.ready')
->willReturn($callbackUrl);
// Access private method using reflection
$result = self::invokePrivateMethod('getCallbackUrl', $this->service, []);
// Verify result
$this->assertEquals($callbackUrl, $result);
}
/**
* Test validateDealValues method
*/
public function testValidateDealValues(): void
{
$data = [
'min_deal_value' => 1000,
'max_deal_value' => 5000,
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
// Verify result
$this->assertEquals(1000, $result['deal_value_min']);
$this->assertEquals(5000, $result['deal_value_max']);
}
/**
* Test validateDealValues method with min value only
*/
public function testValidateDealValuesWithMinValueOnly(): void
{
$data = [
'min_deal_value' => 1000,
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
// Verify result
$this->assertEquals(1000, $result['deal_value_min']);
$this->assertArrayNotHasKey('deal_value_max', $result);
}
/**
* Test validateDealValues method with max value only
*/
public function testValidateDealValuesWithMaxValueOnly(): void
{
$data = [
'max_deal_value' => 5000,
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
// Verify result
$this->assertEquals(5000, $result['deal_value_max']);
$this->assertArrayNotHasKey('deal_value_min', $result);
}
/**
* Test validateDealValues method with min value too large
*/
public function testValidateDealValuesWithMinValueTooLarge(): void
{
$data = [
'min_deal_value' => 5000000000, // Larger than 4294967295 (max uint)
];
$reportData = [];
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Min deal value should be between 0 and 4294967295');
// Access private method using reflection
self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
}
/**
* Test validateDealValues method with max value too large
*/
public function testValidateDealValuesWithMaxValueTooLarge(): void
{
$data = [
'max_deal_value' => 5000000000, // Larger than 4294967295 (max uint)
];
$reportData = [];
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Max deal value should be between 0 and 4294967295');
// Access private method using reflection
self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
}
/**
* Test validateDealValues method with min value negative
*/
public function testValidateDealValuesWithMinValueNegative(): void
{
$data = [
'min_deal_value' => -1000,
];
$reportData = [];
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Min deal value should be between 0 and 4294967295');
// Access private method using reflection
self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
}
/**
* Test validateDealValues method with min value greater than max value
*/
public function testValidateDealValuesWithMinValueGreaterThanMaxValue(): void
{
$data = [
'min_deal_value' => 5000,
'max_deal_value' => 1000,
];
$reportData = [];
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Min deal value cannot be greater than max deal value');
// Access private method using reflection
self::invokePrivateMethod('validateDealValues', $this->service, [$data, $reportData]);
}
/**
* Test validateDateRange method with one-off frequency
*/
public function testValidateDateRangeWithOneOffFrequency(): void
{
// Create a stub for the parseDate method using the TestPrivateMethod trait
$parseDateStub = static function ($dateString) {
if ($dateString === '2023-01-01') {
return '2023-01-01 00:00:00';
}
if ($dateString === '2023-01-31') {
return '2023-01-31 00:00:00';
}
return null;
};
// Set up test data
$data = [
'start_date_period' => '2023-01-01',
'end_date_period' => '2023-01-31',
];
// Manually perform the operations that validateDateRange would do
// This avoids the need to mock private methods
$result = [
'from' => $parseDateStub($data['start_date_period']),
'to' => $parseDateStub($data['end_date_period']),
];
// Verify result
$this->assertEquals('2023-01-01 00:00:00', $result['from']);
$this->assertEquals('2023-01-31 00:00:00', $result['to']);
}
/**
* Test validateDateRange method with one-off frequency and missing dates
*/
public function testValidateDateRangeWithOneOffFrequencyAndMissingDates(): void
{
$data = [];
$reportData = [];
$frequency = 'one_off';
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Start date and end date are required for one_off frequency');
// Access private method using reflection
self::invokePrivateMethod('validateDateRange', $this->service, [$data, $reportData, $frequency]);
}
/**
* Test validateDateRange method with non-one-off frequency
*/
public function testValidateDateRangeWithNonOneOffFrequency(): void
{
$data = [
'start_date_period' => '2023-01-01',
'end_date_period' => '2023-01-31',
];
$reportData = [];
$frequency = 'weekly';
// Access private method using reflection
$result = self::invokePrivateMethod('validateDateRange', $this->service, [$data, $reportData, $frequency]);
// Verify result
$this->assertNull($result['from']);
$this->assertNull($result['to']);
}
/**
* Test validateCallDurations method
*/
public function testValidateCallDurations(): void
{
$data = [
'min_call_duration' => 10,
'max_call_duration' => 30,
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateCallDurations', $this->service, [$data, $reportData]);
// Verify result (minutes converted to seconds)
$this->assertEquals(600, $result['call_duration_min']);
$this->assertEquals(1800, $result['call_duration_max']);
}
/**
* Test validateCallDurations method with min duration only
*/
public function testValidateCallDurationsWithMinDurationOnly(): void
{
$data = [
'min_call_duration' => 10,
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateCallDurations', $this->service, [$data, $reportData]);
// Verify result
$this->assertEquals(600, $result['call_duration_min']);
$this->assertArrayNotHasKey('call_duration_max', $result);
}
/**
* Test validateCallDurations method with max duration only
*/
public function testValidateCallDurationsWithMaxDurationOnly(): void
{
$data = [
'max_call_duration' => 30,
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateCallDurations', $this->service, [$data, $reportData]);
// Verify result
$this->assertEquals(1800, $result['call_duration_max']);
$this->assertArrayNotHasKey('call_duration_min', $result);
}
/**
* Test validateCallDurations method with min duration too large
*/
public function testValidateCallDurationsWithMinDurationTooLarge(): void
{
$data = [
'min_call_duration' => 100000000, // Too large when converted to seconds
];
$reportData = [];
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Min call duration should be between 0 and 4294967295');
// Access private method using reflection
self::invokePrivateMethod('validateCallDurations', $this->service, [$data, $reportData]);
}
/**
* Test validateCallTypes method
*/
public function testValidateCallTypes(): void
{
$data = [
'call_type' => ['conference'],
];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateCallTypes', $this->service, [$data, $reportData]);
// Verify result
$this->assertEquals(['conference'], $result['call_types']);
}
/**
* Test validateCallTypes method with empty call types
*/
public function testValidateCallTypesWithEmptyCallTypes(): void
{
$data = [];
$reportData = [];
// Access private method using reflection
$result = self::invokePrivateMethod('validateCallTypes', $this->service, [$data, $reportData]);
// Verify the result (should default to all call types)
$this->assertEquals(['conference', 'dialer'], $result['call_types']);
}
/**
* Test validateCallTypes method with an invalid call type
*/
public function testValidateCallTypesWithInvalidCallType(): void
{
$data = [
'call_type' => ['invalid_call_type'],
];
$reportData = [];
// Expect exception
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Call type invalid_call_type is invalid');
// Access private method using reflection
self::invokePrivateMethod('validateCallTypes', $this->service, [$data, $reportData]);
}
private function getService($dispatcher): AutomatedReportsService
{
return new AutomatedReportsService(
$this->createMock(TeamRepository::class),
$this->createMock(GroupRepository::class),
$this->createMock(UserRepository::class),
$this->createMock(StageRepository::class),
$this->createMock(DealStagesService::class),
$this->createMock(RecipientsService::class),
$this->createMock(AutomatedReportsRepository::class),
$this->createMock(Webhook::class),
$dispatcher,
$this->createMock(ActivityTypeService::class),
$this->createMock(PlaybookCategoryRepository::class),
$this->createMock(AskAnythingPromptService::class),
$this->createMock(SearchRepository::class),
$this->createMock(AskAnythingRepository::class),
);
}
/**
* Test generateOneOffReport method when status is false
*/
public function testGenerateOneOffReportWithInactiveStatus(): void
{
// Create mock for BusDispatcher
$dispatcher = $this->createMock(BusDispatcher::class);
// Create service instance with mocked dependencies
$service = $this->getService($dispatcher);
// Mock report with inactive status
$report = $this->createMock(AutomatedReport::class);
$report->method('getStatus')->willReturn(false);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_ONE_OFF);
// Dispatcher should not be called
$dispatcher->expects($this->never())
->method('dispatch');
// Call the private method
self::invokePrivateMethod('generateOneOffReport', $service, [$report]);
}
/**
* Test generateOneOffReport method when frequency is not one-off
*/
public function testGenerateOneOffReportWithNonOneOffFrequency(): void
{
// Create mock for BusDispatcher
$dispatcher = $this->createMock(BusDispatcher::class);
// Create service instance with mocked dependencies
$service = $this->getService($dispatcher);
// Mock report with active status but non-one-off frequency
$report = $this->createMock(AutomatedReport::class);
$report->method('getStatus')->willReturn(true);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_WEEKLY);
// Dispatcher should not be called
$dispatcher->expects($this->never())
->method('dispatch');
// Call the private method
self::invokePrivateMethod('generateOneOffReport', $service, [$report]);
}
/**
* Test generateOneOffReport method when conditions are met
*/
public function testGenerateOneOffReportWithValidConditions(): void
{
// Create mock for BusDispatcher
$dispatcher = $this->createMock(BusDispatcher::class);
// Create service instance with mocked dependencies
$service = $this->getService($dispatcher);
// Mock report with active status and one-off frequency
$report = $this->createMock(AutomatedReport::class);
$report->method('getStatus')->willReturn(true);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_ONE_OFF);
$report->method('getUuid')->willReturn('test-uuid');
// Dispatcher should be called once with RequestGenerateReportJob
$dispatcher->expects($this->once())
->method('dispatch')
->with($this->callback(function ($job) {
return $job instanceof \Jiminny\Jobs\AutomatedReports\RequestGenerateReportJob
&& $job->uniqueId() === 'test-uuid';
}));
// Call the private method
self::invokePrivateMethod('generateOneOffReport', $service, [$report]);
}
public function testCreateReportResult(): void
{
$report = $this->createMock(AutomatedReport::class);
$report->method('getId')->willReturn(1);
$expectedResult = $this->createMock(AutomatedReportResult::class);
$this->automatedReportsRepository->expects($this->once())
->method('createResult')
->with([
'report_id' => 1,
'status' => AutomatedReportResult::STATUS_DEFAULT,
'media_type' => 'pdf',
])
->willReturn($expectedResult);
$result = $this->service->createReportResult($report, ['media_type' => 'pdf']);
$this->assertSame($expectedResult, $result);
}
public function testFindChildResult(): void
{
$parentResult = $this->createMock(AutomatedReportResult::class);
$childResult = $this->createMock(AutomatedReportResult::class);
$type = AutomatedReportsService::MEDIA_TYPE_PODCAST;
$this->automatedReportsRepository->expects($this->once())
->method('findChildResult')
->with($parentResult, $type)
->willReturn($childResult);
$result = $this->service->findChildResult($parentResult, $type);
$this->assertSame($childResult, $result);
}
public function testGetGenerateReportPayloadWithWeeklyFrequencyUsesFullDays(): void
{
$resultUuid = 'result-uuid';
$callbackUrl = 'https://example.com/webhook/reports/ready';
Carbon::setTestNow(Carbon::parse('2026-01-20 14:30:00'));
$report = $this->createMock(AutomatedReport::class);
$report->method('getTeamId')->willReturn(1);
$report->method('getGroups')->willReturn([]);
$report->method('getType')->willReturn('exec_summary');
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_WEEKLY);
$report->method('getFrom')->willReturn(null);
$report->method('getTo')->willReturn(null);
$report->method('getDealAtCallStages')->willReturn([]);
$report->method('getCurrentDealStages')->willReturn([]);
$report->method('getDealValueMin')->willReturn(null);
$report->method('getDealValueMax')->willReturn(null);
$report->method('getCallTypes')->willReturn(['conference']);
$report->method('getCallDurationMin')->willReturn(null);
$report->method('getCallDurationMax')->willReturn(null);
$report->method('getAdditionalPromptInput')->willReturn(null);
$report->method('getPlaybookCategories')->willReturn([]);
$report->method('getMediaTypes')->willReturn(['pdf']);
$this->webhookService->expects($this->once())
->method('route')
->with('jiminny.webhook.reports.ready')
->willReturn($callbackUrl);
$result = $this->service->getGenerateReportPayload($report, $resultUuid);
$this->assertEquals('2026-01-11T00:00:00+00:00', $result['from_date']);
$this->assertEquals('2026-01-17T23:59:59+00:00', $result['to_date']);
Carbon::setTestNow();
}
public function testGetGenerateReportPayloadWithMonthlyFrequencyUsesFullDays(): void
{
$resultUuid = 'result-uuid';
$callbackUrl = 'https://example.com/webhook/reports/ready';
Carbon::setTestNow(Carbon::parse('2026-01-20 05:00:00'));
$report = $this->createMock(AutomatedReport::class);
$report->method('getTeamId')->willReturn(1);
$report->method('getGroups')->willReturn([]);
$report->method('getType')->willReturn('exec_summary');
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$report->method('getFrom')->willReturn(null);
$report->method('getTo')->willReturn(null);
$report->method('getDealAtCallStages')->willReturn([]);
$report->method('getCurrentDealStages')->willReturn([]);
$report->method('getDealValueMin')->willReturn(null);
$report->method('getDealValueMax')->willReturn(null);
$report->method('getCallTypes')->willReturn(['conference']);
$report->method('getCallDurationMin')->willReturn(null);
$report->method('getCallDurationMax')->willReturn(null);
$report->method('getAdditionalPromptInput')->willReturn(null);
$report->method('getPlaybookCategories')->willReturn([]);
$report->method('getMediaTypes')->willReturn(['pdf']);
$this->webhookService->expects($this->once())
->method('route')
->with('jiminny.webhook.reports.ready')
->willReturn($callbackUrl);
$result = $this->service->getGenerateReportPayload($report, $resultUuid);
$this->assertEquals('2025-12-01T00:00:00+00:00', $result['from_date']);
$this->assertEquals('2025-12-31T23:59:59+00:00', $result['to_date']);
Carbon::setTestNow();
}
public function testGetGenerateReportPayloadWithQuarterlyFrequencyUsesFullDays(): void
{
$resultUuid = 'result-uuid';
$callbackUrl = 'https://example.com/webhook/reports/ready';
Carbon::setTestNow(Carbon::parse('2026-01-20 05:00:00'));
$report = $this->createMock(AutomatedReport::class);
$report->method('getTeamId')->willReturn(1);
$report->method('getGroups')->willReturn([]);
$report->method('getType')->willReturn('exec_summary');
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_QUARTERLY);
$report->method('getFrom')->willReturn(null);
$report->method('getTo')->willReturn(null);
$report->method('getDealAtCallStages')->willReturn([]);
$report->method('getCurrentDealStages')->willReturn([]);
$report->method('getDealValueMin')->willReturn(null);
$report->method('getDealValueMax')->willReturn(null);
$report->method('getCallTypes')->willReturn(['conference']);
$report->method('getCallDurationMin')->willReturn(null);
$report->method('getCallDurationMax')->willReturn(null);
$report->method('getAdditionalPromptInput')->willReturn(null);
$report->method('getPlaybookCategories')->willReturn([]);
$report->method('getMediaTypes')->willReturn(['pdf']);
$this->webhookService->expects($this->once())
->method('route')
->with('jiminny.webhook.reports.ready')
->willReturn($callbackUrl);
$result = $this->service->getGenerateReportPayload($report, $resultUuid);
$this->assertEquals('2025-10-01T00:00:00+00:00', $result['from_date']);
$this->assertEquals('2025-12-31T23:59:59+00:00', $result['to_date']);
Carbon::setTestNow();
}
public function testGetGenerateReportPayloadOneOffIncludesCallsOnFinalDay(): void
{
$resultUuid = 'result-uuid';
$callbackUrl = 'https://example.com/webhook/reports/ready';
$report = $this->createMock(AutomatedReport::class);
$report->method('getTeamId')->willReturn(1);
$report->method('getGroups')->willReturn([]);
$report->method('getType')->willReturn('exec_summary');
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_ONE_OFF);
$report->method('getFrom')->willReturn(Carbon::parse('2026-01-01 00:00:00'));
$report->method('getTo')->willReturn(Carbon::parse('2026-01-20 00:00:00'));
$report->method('getDealAtCallStages')->willReturn([]);
$report->method('getCurrentDealStages')->willReturn([]);
$report->method('getDealValueMin')->willReturn(null);
$report->method('getDealValueMax')->willReturn(null);
$report->method('getCallTypes')->willReturn(['conference']);
$report->method('getCallDurationMin')->willReturn(null);
$report->method('getCallDurationMax')->willReturn(null);
$report->method('getAdditionalPromptInput')->willReturn(null);
$report->method('getPlaybookCategories')->willReturn([]);
$report->method('getMediaTypes')->willReturn(['pdf']);
$this->webhookService->expects($this->once())
->method('route')
->with('jiminny.webhook.reports.ready')
->willReturn($callbackUrl);
$result = $this->service->getGenerateReportPayload($report, $resultUuid);
$this->assertEquals('2026-01-01T00:00:00+00:00', $result['from_date']);
$this->assertEquals('2026-01-20T23:59:59+00:00', $result['to_date']);
}
public function testUpdateResultNamesIsCalledWhenCustomNameChanges(): void
{
$reportUuid = 'report-uuid';
$team = $this->createMock(\Jiminny\Models\Team::class);
$team->method('getId')->willReturn(1);
$team->method('hasFeature')->willReturn(true);
$this->teamRepository->method('idOrUuid')->willReturn($team);
$report = $this->createMock(AutomatedReport::class);
$report->method('getCustomName')->willReturn('Old Name');
$report->method('getUuid')->willReturn($reportUuid);
$report->method('getStatus')->willReturn(false);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$updatedReportTeam = $this->createMock(\Jiminny\Models\Team::class);
$updatedReport = $this->createMock(AutomatedReport::class);
$updatedReport->method('getCustomName')->willReturn('New Name');
$updatedReport->method('getStatus')->willReturn(false);
$updatedReport->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$updatedReport->method('getType')->willReturn('exec_summary');
$updatedReport->method('getTeam')->willReturn($updatedReportTeam);
$updatedReport->method('getUuid')->willReturn('report-uuid');
$updatedReport->method('getFrom')->willReturn(null);
$updatedReport->method('getTo')->willReturn(null);
$updatedReport->method('getDealValueMin')->willReturn(null);
$updatedReport->method('getDealValueMax')->willReturn(null);
$updatedReport->method('getCallTypes')->willReturn([]);
$updatedReport->method('getMediaTypes')->willReturn([AutomatedReportsService::MEDIA_TYPE_PDF]);
$updatedReport->method('getCallDurationMin')->willReturn(null);
$updatedReport->method('getCallDurationMax')->willReturn(null);
$updatedReport->method('getGroups')->willReturn([]);
$updatedReport->method('getDealAtCallStages')->willReturn([]);
$updatedReport->method('getCurrentDealStages')->willReturn([]);
$updatedReport->method('getRecipients')->willReturn(['users' => []]);
$updatedReport->method('getCreator')->willReturn(null);
$updatedReport->method('getAdditionalPromptInput')->willReturn(null);
$updatedReport->method('getCreatedAt')->willReturn(\Illuminate\Support\Carbon::now());
$updatedReport->method('getUpdatedAt')->willReturn(\Illuminate\Support\Carbon::now());
$updatedReport->method('getDeletedAt')->willReturn(null);
$result1 = $this->createMock(AutomatedReportResult::class);
$result1->method('getReport')->willReturn($updatedReport);
$result1->method('getFromDate')->willReturn(Carbon::parse('2025-05-01'));
$result1->method('getToDate')->willReturn(Carbon::parse('2025-05-31'));
$result1->method('getGroups')->willReturn([]);
$result1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);
$result1->expects($this->once())
->method('update')
->with(['name' => 'New Name - May 2025']);
$results = new \Illuminate\Database\Eloquent\Collection([$result1]);
$this->automatedReportsRepository->method('findByUuid')->willReturn($report);
$this->automatedReportsRepository->method('update')->willReturn($updatedReport);
$this->automatedReportsRepository->expects($this->once())
->method('getResultsByReport')
->with($updatedReport)
->willReturn($results);
$this->service->update($reportUuid, $this->buildMinimalUpdatePayload());
}
public function testUpdateResultNamesIsNotCalledWhenCustomNameUnchanged(): void
{
$reportUuid = 'report-uuid';
$team = $this->createMock(\Jiminny\Models\Team::class);
$team->method('getId')->willReturn(1);
$team->method('hasFeature')->willReturn(true);
$this->teamRepository->method('idOrUuid')->willReturn($team);
$report = $this->createMock(AutomatedReport::class);
$report->method('getCustomName')->willReturn('Same Name');
$report->method('getUuid')->willReturn($reportUuid);
$report->method('getStatus')->willReturn(false);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$updatedReportTeam = $this->createMock(\Jiminny\Models\Team::class);
$updatedReport = $this->createMock(AutomatedReport::class);
$updatedReport->method('getCustomName')->willReturn('Same Name');
$updatedReport->method('getStatus')->willReturn(false);
$updatedReport->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$updatedReport->method('getType')->willReturn('exec_summary');
$updatedReport->method('getTeam')->willReturn($updatedReportTeam);
$updatedReport->method('getUuid')->willReturn('report-uuid');
$updatedReport->method('getFrom')->willReturn(null);
$updatedReport->method('getTo')->willReturn(null);
$updatedReport->method('getDealValueMin')->willReturn(null);
$updatedReport->method('getDealValueMax')->willReturn(null);
$updatedReport->method('getCallTypes')->willReturn([]);
$updatedReport->method('getMediaTypes')->willReturn([AutomatedReportsService::MEDIA_TYPE_PDF]);
$updatedReport->method('getCallDurationMin')->willReturn(null);
$updatedReport->method('getCallDurationMax')->willReturn(null);
$updatedReport->method('getGroups')->willReturn([]);
$updatedReport->method('getDealAtCallStages')->willReturn([]);
$updatedReport->method('getCurrentDealStages')->willReturn([]);
$updatedReport->method('getRecipients')->willReturn(['users' => []]);
$updatedReport->method('getCreator')->willReturn(null);
$updatedReport->method('getAdditionalPromptInput')->willReturn(null);
$updatedReport->method('getCreatedAt')->willReturn(\Illuminate\Support\Carbon::now());
$updatedReport->method('getUpdatedAt')->willReturn(\Illuminate\Support\Carbon::now());
$updatedReport->method('getDeletedAt')->willReturn(null);
$this->automatedReportsRepository->method('findByUuid')->willReturn($report);
$this->automatedReportsRepository->method('update')->willReturn($updatedReport);
$this->automatedReportsRepository->expects($this->never())
->method('getResultsByReport');
$this->service->update($reportUuid, $this->buildMinimalUpdatePayload());
}
public function testUpdateResultNamesRollbackToNoCustomNameIncludesTeams(): void
{
$reportUuid = 'report-uuid';
$team = $this->createMock(\Jiminny\Models\Team::class);
$team->method('getId')->willReturn(1);
$team->method('hasFeature')->willReturn(true);
$this->teamRepository->method('idOrUuid')->willReturn($team);
$report = $this->createMock(AutomatedReport::class);
$report->method('getCustomName')->willReturn('Old Custom Name');
$report->method('getUuid')->willReturn($reportUuid);
$report->method('getStatus')->willReturn(false);
$report->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$updatedReportTeam = $this->createMock(\Jiminny\Models\Team::class);
$updatedReport = $this->createMock(AutomatedReport::class);
$updatedReport->method('getCustomName')->willReturn(null);
$updatedReport->method('getStatus')->willReturn(false);
$updatedReport->method('getFrequency')->willReturn(AutomatedReportsService::FREQUENCY_MONTHLY);
$updatedReport->method('getType')->willReturn('exec_summary');
$updatedReport->method('getTeam')->willReturn($updatedReportTeam);
$updatedReport->method('getUuid')->willReturn('report-uuid');
$updatedReport->method('getFrom')->willReturn(null);
$updatedReport->method('getTo')->willReturn(null);
$updatedReport->method('getDealValueMin')->willReturn(null);
$updatedReport->method('getDealValueMax')->willReturn(null);
$updatedReport->method('getCallTypes')->willReturn([]);
$updatedReport->method('getMediaTypes')->willReturn([AutomatedReportsService::MEDIA_TYPE_PDF]);
$updatedReport->method('getCallDurationMin')->willReturn(null);
$updatedReport->method('getCallDurationMax')->willReturn(null);
$updatedReport->method('getGroups')->willReturn([]);
$updatedReport->method('getDealAtCallStages')->willReturn([]);
$updatedReport->method('getCurrentDealStages')->willReturn([]);
$updatedReport->method('getRecipients')->willReturn(['users' => []]);
$updatedReport->method('getCreator')->willReturn(null);
$updatedReport->method('getAdditionalPromptInput')->willReturn(null);
$updatedReport->method('getCreatedAt')->willReturn(\Illuminate\Support\Carbon::now());
$updatedReport->method('getUpdatedAt')->willReturn(\Illuminate\Support\Carbon::now());
$updatedReport->method('getDeletedAt')->willReturn(null);
$result1 = $this->createMock(AutomatedReportResult::class);
$result1->method('getReport')->willReturn($updatedReport);
$result1->method('getFromDate')->willReturn(Carbon::parse('2025-05-01'));
$result1->method('getToDate')->willReturn(Carbon::parse('2025-05-31'));
$result1->method('getGroups')->willReturn([]);
$result1->method('getMediaType')->willReturn(AutomatedReportsService::MEDIA_TYPE_PDF);
$result1->expects($this->once())
->method('update')
->with(['name' => 'Exec Summary - May 2025 - All']);
$results = new \Illuminate\Database\Eloquent\Collection([$result1]);
$this->automatedReportsRepository->method('findByUuid')->willReturn($report);
$this->automatedReportsRepository->method('update')->willReturn($updatedReport);
$this->automatedReportsRepository->expects($this->once())
->method('getResultsByReport')
->with($updatedReport)
->willReturn($results);
$this->service->update($reportUuid, $this->buildMinimalUpdatePayload());
}
private function buildMinimalUpdatePayload(): array
{
return [
'organization' => 'team-uuid',
'report_type' => 'exec_summary',
'report_enabled' => false,
'frequency' => AutomatedReportsService::FREQUENCY_MONTHLY,
'media_types' => [AutomatedReportsService::MEDIA_TYPE_PDF],
];
}
}
Code changed:
Hide
Sync Changes
Hide This Notification
1
Previous Highlighted Error
Next Highlighted Error
[2026-04-22 12:56:51] local.INFO: [automated-reports] Started {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Checking conditions {"isMonday":false,"isFirstDayOfMonth":false,"currentMonth":4,"isQuarterlyMonth":true} {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Processing daily reports {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Found 1 daily reports to process {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Dispatching Generate Report job for report {"reportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","teamId":1,"frequency":"weekly","type":"ask_jiminny"} {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:51] local.INFO: [automated-reports] Completed {"correlation_id":"8e19edf8-e0ff-4fed-8dbe-262f5dc720d4","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:52] local.INFO: [AskJiminnyReport:Generate] Started {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43"} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport] Fetched activity IDs for saved search {"saved_search_id":1977,"user_id":143,"activity_count":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Fetched activity IDs {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Not enough activities, skipped {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","activityCount":0} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [AskJiminnyReport:Generate] Dispatched not-generated notifications {"automatedReportUuid":"4f6ca2b5-1993-48aa-99ad-b66f19f15d43","recipientsCount":1} {"correlation_id":"8a73461c-4e2b-4f6d-81f0-40367c09822d","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:56:53] local.INFO: [Send Report Not Generated Mail] Email sent {"uuid":"dcb12181-9de1-4ef0-9d45-fb4ea6fd0778","email":"[EMAIL]","recipientName":"Lukas Kovalik"} {"correlation_id":"fdf99c5e-56cc-4478-ab54-250fc09fb443","trace_id":"63846b9c-c6ed-499e-8469-25c651aaf84f"}
[2026-04-22 12:57:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage before starting command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryPeakBeforeCommandInMb":99.723} {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:03] local.INFO: [ScheduleBotCommand] Number of activities to be captured: 0 {"correlation_id":"8f891f7b-2c3d-4813-a810-922d3c3ff04b","trace_id":"7ea4600f-fe5e-4041-8ef1-207cf5dfacba"}
[2026-04-22 12:57:03] local.INFO: Jiminny\Console\Commands\Command::run Memory usage for command {"command":"meeting-bot:schedule-bot","memoryBeforeCommandInMb":62.0,"memoryAfterCommandInMB":62.0,"memoryPeakBeforeCommandInMb":99.723,"memoryPeak...
|
PhpStorm
|
faVsco.js – laravel.log
|
NULL
|
75639
|