OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
361
app/Domain/Reports/Repositories/ReportEngine.php
Normal file
361
app/Domain/Reports/Repositories/ReportEngine.php
Normal file
@@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Leantime\Domain\Reports\Repositories;
|
||||
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Leantime\Core\Db\DatabaseHelper;
|
||||
use Leantime\Core\Db\Db as DbCore;
|
||||
|
||||
/**
|
||||
* Bulk, session-free queries backing the report engine.
|
||||
*
|
||||
* Every method takes an explicit set of project ids plus the requesting user's context
|
||||
* (user id, client id) so the engine can serve project, plan, strategy and — later —
|
||||
* company-level reports without depending on session('currentProject'). The access
|
||||
* predicate mirrors Tickets\Repositories\Tickets::getAllMilestones: assigned via
|
||||
* zp_relationuserproject, open to all, open to the user's client, or requestor role >= 40.
|
||||
*/
|
||||
class ReportEngine
|
||||
{
|
||||
private ConnectionInterface $connection;
|
||||
|
||||
private DatabaseHelper $dbHelper;
|
||||
|
||||
public function __construct(DbCore $db, DatabaseHelper $dbHelper)
|
||||
{
|
||||
$this->connection = $db->getConnection();
|
||||
$this->dbHelper = $dbHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* All milestones of the given projects the requesting user may see. No date filtering —
|
||||
* the service buckets rows into completed/in-progress/overdue/upcoming.
|
||||
*
|
||||
* @param int[] $projectIds
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function getMilestonesForProjects(array $projectIds, int $userId, int $clientId): array
|
||||
{
|
||||
if ($projectIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->connection->table('zp_tickets')
|
||||
->select([
|
||||
'zp_tickets.id',
|
||||
'zp_tickets.headline',
|
||||
'zp_tickets.description',
|
||||
'zp_tickets.outcomeImpact',
|
||||
'zp_tickets.date',
|
||||
'zp_tickets.projectId',
|
||||
'zp_tickets.status',
|
||||
'zp_tickets.editFrom',
|
||||
'zp_tickets.editTo',
|
||||
'zp_tickets.modified',
|
||||
'zp_projects.name as projectName',
|
||||
])
|
||||
->selectRaw("'milestone' AS ".$this->dbHelper->wrapColumn('type'))
|
||||
->selectRaw("CASE WHEN (zp_tickets.tags IS NULL OR zp_tickets.tags = '') THEN 'var(--grey)' ELSE zp_tickets.tags END AS tags")
|
||||
->leftJoin('zp_projects', 'zp_tickets.projectId', '=', 'zp_projects.id')
|
||||
->where('zp_tickets.type', '=', 'milestone')
|
||||
->whereIn('zp_tickets.projectId', $projectIds)
|
||||
->orderBy('zp_tickets.editTo');
|
||||
|
||||
$this->applyAccessPredicate($query, $userId, $clientId);
|
||||
|
||||
return $query->get()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Status-change history rows for the given tickets, oldest first. The service derives
|
||||
* completion dates from the latest transition into a DONE-type status.
|
||||
*
|
||||
* @param int[] $ticketIds
|
||||
* @return array<int, object> Rows with ticketId, changeValue (new status id), dateModified
|
||||
*/
|
||||
public function getStatusHistoryForTickets(array $ticketIds): array
|
||||
{
|
||||
if ($ticketIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->connection->table('zp_tickethistory')
|
||||
->select(['ticketId', 'changeValue', 'dateModified'])
|
||||
->whereIn('ticketId', $ticketIds)
|
||||
->where('changeType', '=', 'status')
|
||||
->orderBy('dateModified')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Due-date change history ("toDate") for the given tickets within a window, oldest first.
|
||||
* Feeds the commitment-integrity view (milestones pushed out of the period).
|
||||
*
|
||||
* @param int[] $ticketIds
|
||||
* @return array<int, object> Rows with ticketId, changeValue (new due date), dateModified
|
||||
*/
|
||||
public function getDueDateChangesForTickets(array $ticketIds, string $fromDb, string $toDb): array
|
||||
{
|
||||
if ($ticketIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->connection->table('zp_tickethistory')
|
||||
->select(['ticketId', 'changeValue', 'dateModified'])
|
||||
->whereIn('ticketId', $ticketIds)
|
||||
->where('changeType', '=', 'toDate')
|
||||
->whereBetween('dateModified', [$fromDb, $toDb])
|
||||
->orderBy('dateModified')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-milestone tickets belonging to the given milestones (for key-task lists and
|
||||
* milestone progress computation).
|
||||
*
|
||||
* @param int[] $milestoneIds
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function getTasksForMilestones(array $milestoneIds): array
|
||||
{
|
||||
if ($milestoneIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->connection->table('zp_tickets')
|
||||
->select([
|
||||
'id',
|
||||
'headline',
|
||||
'status',
|
||||
'projectId',
|
||||
'milestoneid',
|
||||
'storypoints',
|
||||
'priority',
|
||||
'editTo',
|
||||
'dateToFinish',
|
||||
])
|
||||
->whereIn('milestoneid', $milestoneIds)
|
||||
->where('type', '<>', 'milestone')
|
||||
->orderBy('milestoneid')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Project status updates (comments on module "project" carrying a green/yellow/red status)
|
||||
* within the given window, newest first.
|
||||
*
|
||||
* @param int[] $projectIds
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function getStatusUpdatesForProjects(array $projectIds, string $fromDb, string $toDb, int $userId, int $clientId): array
|
||||
{
|
||||
if ($projectIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->connection->table('zp_comment')
|
||||
->select([
|
||||
'zp_comment.id',
|
||||
'zp_comment.moduleId as projectId',
|
||||
'zp_comment.text',
|
||||
'zp_comment.date',
|
||||
'zp_comment.status',
|
||||
'zp_user.firstname as authorFirstname',
|
||||
'zp_user.lastname as authorLastname',
|
||||
'zp_user.profileId as authorProfileId',
|
||||
])
|
||||
->leftJoin('zp_user', 'zp_comment.userId', '=', 'zp_user.id')
|
||||
->leftJoin('zp_projects', 'zp_comment.moduleId', '=', 'zp_projects.id')
|
||||
->where('zp_comment.module', '=', 'project')
|
||||
->whereIn('zp_comment.moduleId', $projectIds)
|
||||
->whereBetween('zp_comment.date', [$fromDb, $toDb])
|
||||
->orderByDesc('zp_comment.date');
|
||||
|
||||
$this->applyAccessPredicate($query, $userId, $clientId);
|
||||
|
||||
return $query->get()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The most recent status update per project regardless of period (feeds the status pill).
|
||||
*
|
||||
* @param int[] $projectIds
|
||||
* @return array<int, object> Keyed by projectId
|
||||
*/
|
||||
public function getLatestStatusUpdateForProjects(array $projectIds): array
|
||||
{
|
||||
if ($projectIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$latest = $this->connection->table('zp_comment')
|
||||
->selectRaw('moduleId, MAX(date) as maxDate')
|
||||
->where('module', '=', 'project')
|
||||
->whereIn('moduleId', $projectIds)
|
||||
->groupBy('moduleId');
|
||||
|
||||
$rows = $this->connection->table('zp_comment')
|
||||
->select([
|
||||
'zp_comment.moduleId as projectId',
|
||||
'zp_comment.text',
|
||||
'zp_comment.date',
|
||||
'zp_comment.status',
|
||||
'zp_user.firstname as authorFirstname',
|
||||
'zp_user.lastname as authorLastname',
|
||||
])
|
||||
->joinSub($latest, 'latest', function ($join) {
|
||||
$join->on('zp_comment.moduleId', '=', 'latest.moduleId')
|
||||
->on('zp_comment.date', '=', 'latest.maxDate');
|
||||
})
|
||||
->leftJoin('zp_user', 'zp_comment.userId', '=', 'zp_user.id')
|
||||
->where('zp_comment.module', '=', 'project')
|
||||
->get()
|
||||
->all();
|
||||
|
||||
$byProject = [];
|
||||
foreach ($rows as $row) {
|
||||
$byProject[(int) $row->projectId] = $row;
|
||||
}
|
||||
|
||||
return $byProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Goals (goal-board canvas items) of the given projects.
|
||||
*
|
||||
* @param int[] $projectIds
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function getGoalsForProjects(array $projectIds, int $userId, int $clientId): array
|
||||
{
|
||||
if ($projectIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->connection->table('zp_canvas_items')
|
||||
->select([
|
||||
'zp_canvas_items.id',
|
||||
'zp_canvas_items.title',
|
||||
'zp_canvas_items.description',
|
||||
'zp_canvas_items.status',
|
||||
'zp_canvas_items.metricType',
|
||||
'zp_canvas_items.startValue',
|
||||
'zp_canvas_items.currentValue',
|
||||
'zp_canvas_items.endValue',
|
||||
'zp_canvas_items.setting',
|
||||
'zp_canvas_items.kpi',
|
||||
'zp_canvas_items.startDate',
|
||||
'zp_canvas_items.endDate',
|
||||
'zp_canvas_items.canvasId',
|
||||
'zp_canvas.projectId',
|
||||
'zp_canvas.title as boardTitle',
|
||||
])
|
||||
->join('zp_canvas', 'zp_canvas_items.canvasId', '=', 'zp_canvas.id')
|
||||
->leftJoin('zp_projects', 'zp_canvas.projectId', '=', 'zp_projects.id')
|
||||
->where('zp_canvas.type', '=', 'goalcanvas')
|
||||
->where('zp_canvas_items.box', '=', 'goal')
|
||||
->whereIn('zp_canvas.projectId', $projectIds)
|
||||
->orderBy('zp_canvas.projectId')
|
||||
->orderBy('zp_canvas_items.sortindex');
|
||||
|
||||
$this->applyAccessPredicate($query, $userId, $clientId);
|
||||
|
||||
return $query->get()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hours logged against the given projects within a window, grouped by project and
|
||||
* milestone (tasks roll up to their milestone; unassigned work groups under 0).
|
||||
*
|
||||
* @param int[] $projectIds
|
||||
* @return array<int, object> Rows with projectId, milestoneId, loggedHours
|
||||
*/
|
||||
public function getHoursLoggedForProjects(array $projectIds, string $fromDb, string $toDb): array
|
||||
{
|
||||
if ($projectIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->connection->table('zp_timesheets')
|
||||
->selectRaw('zp_tickets.projectId AS '.$this->dbHelper->wrapColumn('projectId'))
|
||||
->selectRaw('COALESCE(zp_tickets.milestoneid, 0) AS '.$this->dbHelper->wrapColumn('milestoneId'))
|
||||
->selectRaw('SUM(zp_timesheets.hours) AS '.$this->dbHelper->wrapColumn('loggedHours'))
|
||||
->join('zp_tickets', 'zp_timesheets.ticketId', '=', 'zp_tickets.id')
|
||||
->whereIn('zp_tickets.projectId', $projectIds)
|
||||
->whereBetween('zp_timesheets.workDate', [$fromDb, $toDb])
|
||||
->groupBy('zp_tickets.projectId')
|
||||
->groupByRaw('COALESCE(zp_tickets.milestoneid, 0)')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Core metadata of the given projects the requesting user may see.
|
||||
*
|
||||
* @param int[] $projectIds
|
||||
* @return array<int, object> Keyed by project id
|
||||
*/
|
||||
public function getProjectsMeta(array $projectIds, int $userId, int $clientId): array
|
||||
{
|
||||
if ($projectIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->connection->table('zp_projects')
|
||||
->select([
|
||||
'zp_projects.id',
|
||||
'zp_projects.name',
|
||||
'zp_projects.details',
|
||||
'zp_projects.clientId',
|
||||
'zp_projects.state',
|
||||
'zp_projects.start',
|
||||
'zp_projects.end',
|
||||
'zp_projects.type',
|
||||
'zp_projects.parent',
|
||||
'zp_clients.name as clientName',
|
||||
])
|
||||
->leftJoin('zp_clients', 'zp_projects.clientId', '=', 'zp_clients.id')
|
||||
->whereIn('zp_projects.id', $projectIds);
|
||||
|
||||
$this->applyAccessPredicate($query, $userId, $clientId);
|
||||
|
||||
$byId = [];
|
||||
foreach ($query->get()->all() as $row) {
|
||||
$byId[(int) $row->id] = $row;
|
||||
}
|
||||
|
||||
return $byId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the standard project access predicate: project assigned to the user, open to
|
||||
* everyone, open to the user's client, or the requesting user is admin/owner (role >= 40).
|
||||
* The query must already join zp_projects.
|
||||
*/
|
||||
private function applyAccessPredicate(Builder $query, int $userId, int $clientId): void
|
||||
{
|
||||
$query->leftJoin('zp_user as requestor', function ($join) use ($userId) {
|
||||
$join->on('requestor.id', '=', $this->connection->raw((string) $userId));
|
||||
});
|
||||
|
||||
$query->where(function ($q) use ($userId, $clientId) {
|
||||
$q->whereIn('zp_projects.id', function ($subquery) use ($userId) {
|
||||
$subquery->select('projectId')
|
||||
->from('zp_relationuserproject')
|
||||
->where('zp_relationuserproject.userId', $userId);
|
||||
})
|
||||
->orWhere('zp_projects.psettings', 'all')
|
||||
->orWhere(function ($q2) use ($clientId) {
|
||||
$q2->where('zp_projects.psettings', 'clients')
|
||||
->where('zp_projects.clientId', $clientId);
|
||||
})
|
||||
->orWhere('requestor.role', '>=', 40);
|
||||
});
|
||||
}
|
||||
}
|
||||
301
app/Domain/Reports/Repositories/Reports.php
Normal file
301
app/Domain/Reports/Repositories/Reports.php
Normal file
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Reports\Repositories;
|
||||
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Leantime\Core\Db\DatabaseHelper;
|
||||
use Leantime\Core\Db\Db as DbCore;
|
||||
use Leantime\Domain\Projects\Repositories\Projects as ProjectRepository;
|
||||
use Leantime\Domain\Reports\Models\Reports as ReportsModel;
|
||||
use Leantime\Domain\Tickets\Repositories\Tickets as TicketRepository;
|
||||
|
||||
class Reports
|
||||
{
|
||||
private ConnectionInterface $db;
|
||||
|
||||
private DatabaseHelper $dbHelper;
|
||||
|
||||
/**
|
||||
* __construct - get database connection
|
||||
*/
|
||||
public function __construct(DbCore $db, DatabaseHelper $dbHelper)
|
||||
{
|
||||
$this->db = $db->getConnection();
|
||||
$this->dbHelper = $dbHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run ticket report for a project and optionally a sprint
|
||||
*
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function runTicketReport(int $projectId, string $sprintId): array|bool
|
||||
{
|
||||
$ticketRepo = app()->make(TicketRepository::class);
|
||||
$statusGroupsSQL = $ticketRepo->getStatusListGroupedByType($projectId);
|
||||
|
||||
// Parse status groups from SQL format to arrays for cross-database compatibility
|
||||
$statusGroups = $this->dbHelper->parseStatusGroups($statusGroupsSQL);
|
||||
|
||||
// Build cross-database compatible date expression
|
||||
$yesterdayDate = $this->dbHelper->yesterdayDate();
|
||||
|
||||
// Build cross-database compatible string aggregation
|
||||
$ticketIds = $this->dbHelper->stringAggregate('zp_tickets.id');
|
||||
|
||||
// Build query with query builder
|
||||
$query = $this->db->table('zp_tickets');
|
||||
|
||||
// Select sprint column or -1 based on whether sprintId is provided
|
||||
if ($sprintId !== '') {
|
||||
$query->selectRaw('sprint AS '.$this->dbHelper->wrapColumn('sprintId'));
|
||||
} else {
|
||||
$query->selectRaw('-1 AS '.$this->dbHelper->wrapColumn('sprintId'));
|
||||
}
|
||||
|
||||
// Build the select statement with cross-database functions
|
||||
$query->selectRaw($this->dbHelper->wrapColumn('projectId'))
|
||||
->selectRaw("{$yesterdayDate} AS date")
|
||||
->selectRaw('COUNT(DISTINCT zp_tickets.id) AS sum_todos')
|
||||
->selectRaw(
|
||||
'SUM(CASE WHEN status IN ('.implode(',', $statusGroups['NEW'] ?: [0]).') THEN 1 ELSE 0 END) AS sum_open_todos'
|
||||
)
|
||||
->selectRaw(
|
||||
'SUM(CASE WHEN status IN ('.implode(',', $statusGroups['INPROGRESS'] ?: [0]).') THEN 1 ELSE 0 END) AS sum_progres_todos'
|
||||
)
|
||||
->selectRaw(
|
||||
'SUM(CASE WHEN status IN ('.implode(',', $statusGroups['DONE'] ?: [0]).') THEN 1 ELSE 0 END) AS sum_closed_todos'
|
||||
)
|
||||
->selectRaw('SUM('.$this->dbHelper->wrapColumn('planHours').') AS sum_planned_hours')
|
||||
->selectRaw('SUM('.$this->dbHelper->wrapColumn('hourRemaining').') AS sum_estremaining_hours')
|
||||
->selectRaw('SUM(zp_tickets.storypoints) AS sum_points')
|
||||
->selectRaw(
|
||||
'SUM(CASE WHEN status IN ('.implode(',', $statusGroups['NEW'] ?: [0]).') THEN zp_tickets.storypoints ELSE 0 END) AS sum_points_open'
|
||||
)
|
||||
->selectRaw(
|
||||
'SUM(CASE WHEN status IN ('.implode(',', $statusGroups['INPROGRESS'] ?: [0]).') THEN zp_tickets.storypoints ELSE 0 END) AS sum_points_progress'
|
||||
)
|
||||
->selectRaw(
|
||||
'SUM(CASE WHEN status IN ('.implode(',', $statusGroups['DONE'] ?: [0]).') THEN zp_tickets.storypoints ELSE 0 END) AS sum_points_done'
|
||||
)
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints = 1 THEN 1 ELSE 0 END) AS sum_todos_xs')
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints = 2 THEN 1 ELSE 0 END) AS sum_todos_s')
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints = 3 THEN 1 ELSE 0 END) AS sum_todos_m')
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints = 5 THEN 1 ELSE 0 END) AS sum_todos_l')
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints = 8 THEN 1 ELSE 0 END) AS sum_todos_xl')
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints = 13 THEN 1 ELSE 0 END) AS sum_todos_xxl')
|
||||
->selectRaw('SUM(CASE WHEN zp_tickets.storypoints IS NULL OR zp_tickets.storypoints = 0 THEN 1 ELSE 0 END) AS sum_todos_none')
|
||||
->selectRaw("{$ticketIds} AS tickets")
|
||||
->selectRaw('SUM('.$this->dbHelper->wrapColumn('planHours').') / COUNT(zp_tickets.id) AS daily_avg_hours_planned_todo')
|
||||
->selectRaw('SUM('.$this->dbHelper->wrapColumn('planHours').') / NULLIF(SUM(zp_tickets.storypoints), 0) AS daily_avg_hours_planned_point')
|
||||
->selectRaw('SUM('.$this->dbHelper->wrapColumn('hourRemaining').') / COUNT(zp_tickets.id) AS daily_avg_hours_remaining_todo')
|
||||
->selectRaw('SUM('.$this->dbHelper->wrapColumn('hourRemaining').') / NULLIF(SUM(zp_tickets.storypoints), 0) AS daily_avg_hours_remaining_point')
|
||||
->where('projectId', $projectId)
|
||||
->where('zp_tickets.type', '<>', 'subtask')
|
||||
->where('zp_tickets.type', '<>', 'milestone');
|
||||
|
||||
// Add sprint filter if provided
|
||||
if ($sprintId !== '') {
|
||||
$query->where('sprint', $sprintId)
|
||||
->groupBy('projectId', 'sprint');
|
||||
} else {
|
||||
$query->groupBy('projectId');
|
||||
}
|
||||
|
||||
$result = $query->first();
|
||||
$valuesTickets = $result ? (array) $result : false;
|
||||
|
||||
$storyPoints = isset($valuesTickets['sum_points']) && $valuesTickets['sum_points'] > 0
|
||||
? $valuesTickets['sum_points']
|
||||
: 1;
|
||||
|
||||
// Timesheet Reports using query builder
|
||||
$timesheetQuery = $this->db->table('zp_tickets')
|
||||
->selectRaw('ROUND(CAST(SUM(zp_timesheets.hours) AS DECIMAL(10,2)), 2) AS sum_logged_hours')
|
||||
->selectRaw('ROUND(CAST(SUM(zp_timesheets.hours) / NULLIF(COUNT(DISTINCT zp_tickets.id), 0) AS DECIMAL(10,2)), 2) AS daily_avg_hours_booked_todo')
|
||||
->selectRaw('ROUND(CAST(SUM(zp_timesheets.hours) / ? AS DECIMAL(10,2)), 2) AS daily_avg_hours_booked_point', [$storyPoints])
|
||||
->leftJoin('zp_timesheets', 'zp_tickets.id', '=', 'zp_timesheets.ticketId')
|
||||
->where('projectId', $projectId)
|
||||
->where('zp_tickets.type', '<>', 'subtask')
|
||||
->where('zp_tickets.type', '<>', 'milestone');
|
||||
|
||||
if ($sprintId !== '') {
|
||||
$timesheetQuery->where('sprint', $sprintId)
|
||||
->groupBy('projectId', 'sprint');
|
||||
} else {
|
||||
$timesheetQuery->groupBy('projectId');
|
||||
}
|
||||
|
||||
$timesheetResult = $timesheetQuery->first();
|
||||
$valueTimesheets = $timesheetResult ? (array) $timesheetResult : false;
|
||||
|
||||
// Number of users
|
||||
$projectService = app()->make(ProjectRepository::class);
|
||||
$users = $projectService->getUsersAssignedToProject($projectId);
|
||||
|
||||
$numberOfUsers = is_array($users) ? count($users) : 0;
|
||||
|
||||
if (is_array($valuesTickets) && is_array($valueTimesheets)) {
|
||||
$values = array_merge($valuesTickets, $valueTimesheets);
|
||||
$values['sum_teammembers'] = $numberOfUsers;
|
||||
} else {
|
||||
$values = false;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function checkLastReportEntries(int $projectId): false|array
|
||||
{
|
||||
$results = $this->db->table('zp_stats')
|
||||
->whereRaw($this->dbHelper->isYesterday('date'))
|
||||
->where('projectId', $projectId)
|
||||
->limit(2)
|
||||
->get();
|
||||
|
||||
return $results->map(function ($row) {
|
||||
$report = new ReportsModel;
|
||||
foreach ((array) $row as $key => $value) {
|
||||
if (property_exists($report, $key)) {
|
||||
$report->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $report;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function addReport(array|object $report): void
|
||||
{
|
||||
$report = (object) $report;
|
||||
|
||||
$this->db->table('zp_stats')->insert([
|
||||
'sprintId' => $report->sprintId,
|
||||
'projectId' => $report->projectId,
|
||||
'date' => $report->date,
|
||||
'sum_todos' => $report->sum_todos,
|
||||
'sum_open_todos' => $report->sum_open_todos,
|
||||
'sum_progres_todos' => $report->sum_progres_todos,
|
||||
'sum_closed_todos' => $report->sum_closed_todos,
|
||||
'sum_planned_hours' => $report->sum_planned_hours,
|
||||
'sum_estremaining_hours' => $report->sum_estremaining_hours,
|
||||
'sum_logged_hours' => $report->sum_logged_hours,
|
||||
'sum_points' => $report->sum_points,
|
||||
'sum_points_done' => $report->sum_points_done,
|
||||
'sum_points_progress' => $report->sum_points_progress,
|
||||
'sum_points_open' => $report->sum_points_open,
|
||||
'sum_todos_xs' => $report->sum_todos_xs,
|
||||
'sum_todos_s' => $report->sum_todos_s,
|
||||
'sum_todos_m' => $report->sum_todos_m,
|
||||
'sum_todos_l' => $report->sum_todos_l,
|
||||
'sum_todos_xl' => $report->sum_todos_xl,
|
||||
'sum_todos_xxl' => $report->sum_todos_xxl,
|
||||
'sum_todos_none' => $report->sum_todos_none,
|
||||
'tickets' => $report->tickets,
|
||||
'daily_avg_hours_booked_todo' => $report->daily_avg_hours_booked_todo,
|
||||
'daily_avg_hours_booked_point' => $report->daily_avg_hours_booked_point,
|
||||
'daily_avg_hours_planned_todo' => $report->daily_avg_hours_planned_todo,
|
||||
'daily_avg_hours_planned_point' => $report->daily_avg_hours_planned_point,
|
||||
'daily_avg_hours_remaining_point' => $report->daily_avg_hours_remaining_point,
|
||||
'daily_avg_hours_remaining_todo' => $report->daily_avg_hours_remaining_todo,
|
||||
'sum_teammembers' => $report->sum_teammembers,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSprintReport(int $sprint): array|false
|
||||
{
|
||||
$results = $this->db->table('zp_stats')
|
||||
->where('sprintId', $sprint)
|
||||
->orderBy('date', 'asc')
|
||||
->get();
|
||||
|
||||
return $results->map(function ($row) {
|
||||
$report = new ReportsModel;
|
||||
foreach ((array) $row as $key => $value) {
|
||||
if (property_exists($report, $key)) {
|
||||
$report->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $report;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function getBacklogReport(int $project): false|array
|
||||
{
|
||||
$results = $this->db->table('zp_stats')
|
||||
->where('projectId', $project)
|
||||
->where('sprintId', 0)
|
||||
->orderBy('date', 'asc')
|
||||
->limit(95)
|
||||
->get();
|
||||
|
||||
return $results->map(function ($row) {
|
||||
$report = new ReportsModel;
|
||||
foreach ((array) $row as $key => $value) {
|
||||
if (property_exists($report, $key)) {
|
||||
$report->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $report;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
public function getFullReport(int $project): false|array
|
||||
{
|
||||
$results = $this->db->table('zp_stats')
|
||||
->select(
|
||||
'date',
|
||||
$this->db->raw('SUM(sum_todos) AS sum_todos'),
|
||||
$this->db->raw('SUM(sum_open_todos) AS sum_open_todos'),
|
||||
$this->db->raw('SUM(sum_progres_todos) AS sum_progres_todos'),
|
||||
$this->db->raw('SUM(sum_closed_todos) AS sum_closed_todos'),
|
||||
$this->db->raw('SUM(sum_planned_hours) AS sum_planned_hours'),
|
||||
$this->db->raw('SUM(sum_estremaining_hours) AS sum_estremaining_hours'),
|
||||
$this->db->raw('ROUND(CAST(SUM(sum_logged_hours) AS DECIMAL(10,2)), 2) AS sum_logged_hours'),
|
||||
$this->db->raw('SUM(sum_points) AS sum_points'),
|
||||
$this->db->raw('SUM(sum_points_done) AS sum_points_done'),
|
||||
$this->db->raw('SUM(sum_points_progress) AS sum_points_progress'),
|
||||
$this->db->raw('SUM(sum_points_open) AS sum_points_open'),
|
||||
$this->db->raw('SUM(sum_todos_xs) AS sum_todos_xs'),
|
||||
$this->db->raw('SUM(sum_todos_s) AS sum_todos_s'),
|
||||
$this->db->raw('SUM(sum_todos_m) AS sum_todos_m'),
|
||||
$this->db->raw('SUM(sum_todos_l) AS sum_todos_l'),
|
||||
$this->db->raw('SUM(sum_todos_xl) AS sum_todos_xl'),
|
||||
$this->db->raw('SUM(sum_todos_xxl) AS sum_todos_xxl'),
|
||||
$this->db->raw('SUM(sum_todos_none) AS sum_todos_none'),
|
||||
$this->db->raw('SUM('.$this->dbHelper->castAs('tickets', 'integer').') AS tickets'),
|
||||
$this->db->raw('SUM(daily_avg_hours_booked_todo) AS daily_avg_hours_booked_todo'),
|
||||
$this->db->raw('SUM(daily_avg_hours_booked_point) AS daily_avg_hours_booked_point'),
|
||||
$this->db->raw('SUM(daily_avg_hours_planned_todo) AS daily_avg_hours_planned_todo'),
|
||||
$this->db->raw('SUM(daily_avg_hours_planned_point) AS daily_avg_hours_planned_point'),
|
||||
$this->db->raw('SUM(daily_avg_hours_remaining_point) AS daily_avg_hours_remaining_point'),
|
||||
$this->db->raw('SUM(daily_avg_hours_remaining_todo) AS daily_avg_hours_remaining_todo')
|
||||
)
|
||||
->where('projectId', $project)
|
||||
->where(function ($query) {
|
||||
$query->where('sprintId', '<', 1)
|
||||
->orWhereNull('sprintId');
|
||||
})
|
||||
->groupBy('date')
|
||||
->orderBy('date', 'desc')
|
||||
->limit(120)
|
||||
->get();
|
||||
|
||||
return $results->map(function ($row) {
|
||||
$report = new ReportsModel;
|
||||
foreach ((array) $row as $key => $value) {
|
||||
if (property_exists($report, $key)) {
|
||||
$report->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $report;
|
||||
})->toArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user