OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
145
app/Domain/Timesheets/Tools/GetProjectTimesheetsTool.php
Normal file
145
app/Domain/Timesheets/Tools/GetProjectTimesheetsTool.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Auth\Models\Roles;
|
||||
use Leantime\Domain\Projects\Services\Projects;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Get timesheet entries for an entire project.
|
||||
*/
|
||||
#[IsReadOnly]
|
||||
class GetProjectTimesheetsTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
private Projects $projectsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->integer('projectId')->description('Project ID to get timesheets for.')
|
||||
->required()
|
||||
->string('dateFrom')->description('Start date in ISO8601 format.')
|
||||
->required()
|
||||
->string('dateTo')->description('End date in ISO8601 format.')
|
||||
->required()
|
||||
->integer('userId')->description('Filter by specific user ID (optional).');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'getProjectTimesheets';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Gets timesheet entries for an entire project.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
$projectId = (int) ($arguments['projectId'] ?? 0);
|
||||
|
||||
$globalRole = session('userdata.role');
|
||||
if (! in_array($globalRole, [Roles::$manager, Roles::$admin, Roles::$owner], true)) {
|
||||
// getProjectRole() returns a stored role key (usually a numeric string like "30");
|
||||
// resolve it to a role name before comparing (same pattern as
|
||||
// Tickets::userIsAtLeastForProject).
|
||||
$projectRoleKey = $this->projectsService->getProjectRole(session('userdata.id'), $projectId);
|
||||
$projectRole = ctype_digit((string) $projectRoleKey)
|
||||
? Roles::getRoleString((int) $projectRoleKey)
|
||||
: $projectRoleKey;
|
||||
|
||||
if ($projectRole !== Roles::$manager) {
|
||||
return ToolResult::error("You don't have permission to view project-wide timesheet data. This requires a manager role or above, or a manager role in this project.");
|
||||
}
|
||||
}
|
||||
|
||||
$fromDate = dtHelper()->parseUserDateTime($arguments['dateFrom']);
|
||||
$toDate = dtHelper()->parseUserDateTime($arguments['dateTo']);
|
||||
$userId = ($arguments['userId'] ?? null);
|
||||
|
||||
$timesheets = $this->timesheetsService->getAll(
|
||||
dateFrom: $fromDate,
|
||||
dateTo: $toDate,
|
||||
projectId: $projectId,
|
||||
userId: $userId
|
||||
);
|
||||
|
||||
if (empty($timesheets)) {
|
||||
return ToolResult::text('No timesheet entries found for this project in the specified date range.');
|
||||
}
|
||||
|
||||
$project = $this->projectsService->getProject($projectId);
|
||||
|
||||
$response = '## Project Timesheet Summary: '.$project['name']."\n";
|
||||
$response .= 'From: '.$fromDate->formatDateForUser().' to '.$toDate->formatDateForUser()."\n\n";
|
||||
|
||||
$totalHours = 0;
|
||||
$userTotals = [];
|
||||
$ticketTotals = [];
|
||||
$kindTotals = [];
|
||||
|
||||
foreach ($timesheets as $entry) {
|
||||
$hours = (float) $entry['hours'];
|
||||
$userName = $entry['firstname'].' '.$entry['lastname'];
|
||||
$entryUserId = $entry['userId'];
|
||||
$ticketId = $entry['ticketId'];
|
||||
$ticketTitle = $entry['headline'] ?? 'No ticket';
|
||||
$kind = $entry['kind'];
|
||||
|
||||
$totalHours += $hours;
|
||||
|
||||
if (! isset($userTotals[$entryUserId])) {
|
||||
$userTotals[$entryUserId] = [
|
||||
'name' => $userName,
|
||||
'hours' => 0,
|
||||
];
|
||||
}
|
||||
$userTotals[$entryUserId]['hours'] += $hours;
|
||||
|
||||
if (! isset($ticketTotals[$ticketId])) {
|
||||
$ticketTotals[$ticketId] = [
|
||||
'title' => $ticketTitle,
|
||||
'hours' => 0,
|
||||
];
|
||||
}
|
||||
$ticketTotals[$ticketId]['hours'] += $hours;
|
||||
|
||||
if (! isset($kindTotals[$kind])) {
|
||||
$kindTotals[$kind] = 0;
|
||||
}
|
||||
$kindTotals[$kind] += $hours;
|
||||
}
|
||||
|
||||
$response .= '### Total Hours: '.number_format($totalHours, 2)."\n\n";
|
||||
|
||||
$response .= "### Hours by Team Member\n";
|
||||
foreach ($userTotals as $user) {
|
||||
$response .= '- **'.$user['name'].'**: '.number_format($user['hours'], 2)." hours\n";
|
||||
}
|
||||
|
||||
$response .= "\n### Hours by Task\n";
|
||||
foreach ($ticketTotals as $ticketId => $ticket) {
|
||||
$response .= '- **'.$ticket['title'].'**: '.number_format($ticket['hours'], 2)." hours\n";
|
||||
}
|
||||
|
||||
$response .= "\n### Hours by Type\n";
|
||||
foreach ($kindTotals as $kindKey => $hours) {
|
||||
$kindLabel = $this->timesheetsService->getLoggableHourTypes()[$kindKey] ?? $kindKey;
|
||||
$response .= '- **'.$kindLabel.'**: '.number_format($hours, 2)." hours\n";
|
||||
}
|
||||
|
||||
return ToolResult::text($response);
|
||||
}
|
||||
}
|
||||
149
app/Domain/Timesheets/Tools/GetTimesheetSummaryTool.php
Normal file
149
app/Domain/Timesheets/Tools/GetTimesheetSummaryTool.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Get a summary of logged hours grouped by different criteria.
|
||||
*/
|
||||
#[IsReadOnly]
|
||||
class GetTimesheetSummaryTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->string('dateFrom')->description('Start date in ISO8601 format.')
|
||||
->required()
|
||||
->string('dateTo')->description('End date in ISO8601 format.')
|
||||
->required()
|
||||
->string('groupBy')
|
||||
->description('How to group the data (project, user, day, week, ticket, kind).')
|
||||
->required()
|
||||
->integer('projectId')->description('Project ID to filter by (optional).')
|
||||
->integer('userId')->description('User ID to filter by (optional, managers only).');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'getTimesheetSummary';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Gets a summary of logged hours grouped by different criteria.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
$currentUserId = (int) session('userdata.id');
|
||||
$userRole = session('userdata.role');
|
||||
|
||||
// User ID 0 (or omitted) means the current user per server instructions
|
||||
$userId = (int) ($arguments['userId'] ?? 0) ?: $currentUserId;
|
||||
|
||||
if ($userId !== $currentUserId && ! in_array($userRole, ['admin', 'manager', 'owner'])) {
|
||||
return ToolResult::error("You don't have permission to view other users' timesheet data.");
|
||||
}
|
||||
|
||||
$fromDate = dtHelper()->parseUserDateTime($arguments['dateFrom']);
|
||||
$toDate = dtHelper()->parseUserDateTime($arguments['dateTo']);
|
||||
$groupBy = $arguments['groupBy'];
|
||||
$projectId = ($arguments['projectId'] ?? null);
|
||||
$targetUserId = $userId;
|
||||
|
||||
$timesheets = $this->timesheetsService->getAll(
|
||||
dateFrom: $fromDate,
|
||||
dateTo: $toDate,
|
||||
projectId: $projectId ?? -1,
|
||||
kind: 'all',
|
||||
userId: $targetUserId
|
||||
);
|
||||
|
||||
if (empty($timesheets)) {
|
||||
return ToolResult::text('No timesheet entries found for the specified criteria.');
|
||||
}
|
||||
|
||||
$response = "## Timesheet Summary\n";
|
||||
$response .= 'From: '.$fromDate->formatDateForUser().' to '.$toDate->formatDateForUser()."\n\n";
|
||||
|
||||
$totalHours = 0;
|
||||
$groupedData = [];
|
||||
|
||||
foreach ($timesheets as $entry) {
|
||||
$hours = (float) $entry['hours'];
|
||||
$totalHours += $hours;
|
||||
|
||||
$groupKey = '';
|
||||
$groupLabel = '';
|
||||
|
||||
switch ($groupBy) {
|
||||
case 'project':
|
||||
$groupKey = $entry['projectId'];
|
||||
$groupLabel = $entry['name'];
|
||||
break;
|
||||
case 'user':
|
||||
$groupKey = $entry['userId'];
|
||||
$groupLabel = $entry['firstname'].' '.$entry['lastname'];
|
||||
break;
|
||||
case 'day':
|
||||
$date = dtHelper()->parseDbDateTime($entry['workDate']);
|
||||
$groupKey = $date->format('Y-m-d');
|
||||
$groupLabel = $date->formatDateForUser();
|
||||
break;
|
||||
case 'week':
|
||||
$date = dtHelper()->parseDbDateTime($entry['workDate']);
|
||||
$weekStart = $date->startOfWeek()->format('Y-m-d');
|
||||
$groupKey = $weekStart;
|
||||
$groupLabel = 'Week of '.$date->startOfWeek()->formatDateForUser();
|
||||
break;
|
||||
case 'ticket':
|
||||
$groupKey = $entry['ticketId'];
|
||||
$groupLabel = $entry['headline'] ?? 'No ticket';
|
||||
break;
|
||||
case 'kind':
|
||||
$groupKey = $entry['kind'];
|
||||
$kindLabel = $this->timesheetsService->getLoggableHourTypes()[$entry['kind']] ?? $entry['kind'];
|
||||
$groupLabel = $kindLabel;
|
||||
break;
|
||||
default:
|
||||
$groupKey = 'all';
|
||||
$groupLabel = 'All Entries';
|
||||
}
|
||||
|
||||
if (! isset($groupedData[$groupKey])) {
|
||||
$groupedData[$groupKey] = [
|
||||
'label' => $groupLabel,
|
||||
'hours' => 0,
|
||||
];
|
||||
}
|
||||
$groupedData[$groupKey]['hours'] += $hours;
|
||||
}
|
||||
|
||||
uasort($groupedData, function ($a, $b) {
|
||||
return $b['hours'] <=> $a['hours'];
|
||||
});
|
||||
|
||||
$response .= '### Total Hours: '.number_format($totalHours, 2)."\n\n";
|
||||
|
||||
$response .= '### Hours by '.ucfirst($groupBy)."\n";
|
||||
foreach ($groupedData as $group) {
|
||||
$percentage = ($group['hours'] / $totalHours) * 100;
|
||||
$response .= '- **'.$group['label'].'**: '.number_format($group['hours'], 2).
|
||||
' hours ('.number_format($percentage, 1)."%)\n";
|
||||
}
|
||||
|
||||
return ToolResult::text($response);
|
||||
}
|
||||
}
|
||||
111
app/Domain/Timesheets/Tools/GetUserTimesheetsTool.php
Normal file
111
app/Domain/Timesheets/Tools/GetUserTimesheetsTool.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Get timesheet entries for the current user.
|
||||
*/
|
||||
#[IsReadOnly]
|
||||
class GetUserTimesheetsTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->string('dateFrom')->description('Start date in ISO8601 format.')
|
||||
->required()
|
||||
->string('dateTo')->description('End date in ISO8601 format.')
|
||||
->required()
|
||||
->integer('projectId')->description('Project ID to filter by (optional).');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'getUserTimesheets';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Gets timesheet entries for the current user within a specified date range.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
$fromDate = dtHelper()->parseUserDateTime($arguments['dateFrom']);
|
||||
$toDate = dtHelper()->parseUserDateTime($arguments['dateTo']);
|
||||
|
||||
$userId = session('userdata.id');
|
||||
$projectId = ($arguments['projectId'] ?? null);
|
||||
|
||||
$timesheets = $this->timesheetsService->getAll(
|
||||
dateFrom: $fromDate,
|
||||
dateTo: $toDate,
|
||||
projectId: $projectId ?? -1,
|
||||
userId: $userId
|
||||
);
|
||||
|
||||
if (empty($timesheets)) {
|
||||
return ToolResult::text('No timesheet entries found for the specified date range.');
|
||||
}
|
||||
|
||||
$response = "## Your Timesheet Entries\n";
|
||||
$response .= 'From: '.$fromDate->formatDateForUser().' to '.$toDate->formatDateForUser()."\n\n";
|
||||
|
||||
$totalHours = 0;
|
||||
$projectTotals = [];
|
||||
|
||||
foreach ($timesheets as $entry) {
|
||||
$entryProjectId = $entry['projectId'];
|
||||
$projectName = $entry['name'];
|
||||
$hours = (float) $entry['hours'];
|
||||
$date = dtHelper()->parseDbDateTime($entry['workDate'])->formatDateForUser();
|
||||
$ticketTitle = $entry['headline'] ?? 'No ticket';
|
||||
$description = Str::sanitizeForLLM($entry['description'] ?? '');
|
||||
$kind = $entry['kind'];
|
||||
|
||||
$result = [
|
||||
'date' => $date,
|
||||
'project' => $projectName,
|
||||
'ticket' => $ticketTitle,
|
||||
'hours' => $hours,
|
||||
'type' => $kind,
|
||||
'description' => $description,
|
||||
];
|
||||
|
||||
$response .= Str::toMarkdown($result)."\n";
|
||||
|
||||
$totalHours += $hours;
|
||||
|
||||
if (! isset($projectTotals[$entryProjectId])) {
|
||||
$projectTotals[$entryProjectId] = [
|
||||
'name' => $projectName,
|
||||
'hours' => 0,
|
||||
];
|
||||
}
|
||||
$projectTotals[$entryProjectId]['hours'] += $hours;
|
||||
}
|
||||
|
||||
$response .= "\n## Summary\n";
|
||||
$response .= 'Total Hours: '.number_format($totalHours, 2)."\n\n";
|
||||
|
||||
$response .= "### Hours by Project\n";
|
||||
foreach ($projectTotals as $project) {
|
||||
$response .= '- **'.$project['name'].'**: '.number_format($project['hours'], 2)." hours\n";
|
||||
}
|
||||
|
||||
return ToolResult::text($response);
|
||||
}
|
||||
}
|
||||
102
app/Domain/Timesheets/Tools/GetWeeklyTimesheetsTool.php
Normal file
102
app/Domain/Timesheets/Tools/GetWeeklyTimesheetsTool.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Get a weekly view of timesheet entries.
|
||||
*/
|
||||
#[IsReadOnly]
|
||||
class GetWeeklyTimesheetsTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->string('weekStart')->description('Start date of the week in ISO8601 format.')
|
||||
->required()
|
||||
->integer('projectId')->description('Project ID to filter by (optional).')
|
||||
->integer('userId')->description('User ID to filter by (optional, managers only).');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'getWeeklyTimesheets';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Gets a weekly view of timesheet entries.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
$currentUserId = (int) session('userdata.id');
|
||||
$userRole = session('userdata.role');
|
||||
|
||||
// User ID 0 (or omitted) means the current user per server instructions
|
||||
$userId = (int) ($arguments['userId'] ?? 0) ?: $currentUserId;
|
||||
|
||||
if ($userId !== $currentUserId && ! in_array($userRole, ['admin', 'manager', 'owner'])) {
|
||||
return ToolResult::error("You don't have permission to view other users' timesheet data.");
|
||||
}
|
||||
|
||||
$fromDate = dtHelper()->parseUserDateTime($arguments['weekStart'])->startOfWeek();
|
||||
$projectId = ($arguments['projectId'] ?? null);
|
||||
$targetUserId = $userId;
|
||||
|
||||
$timesheetGroups = $this->timesheetsService->getWeeklyTimesheets(
|
||||
projectId: $projectId ?? -1,
|
||||
fromDate: $fromDate,
|
||||
userId: $targetUserId
|
||||
);
|
||||
|
||||
if (empty($timesheetGroups)) {
|
||||
return ToolResult::text('No timesheet entries found for the specified week.');
|
||||
}
|
||||
|
||||
$weekEnd = $fromDate->addDays(6);
|
||||
$response = "## Weekly Timesheet\n";
|
||||
$response .= 'Week of '.$fromDate->formatDateForUser().' to '.$weekEnd->formatDateForUser()."\n\n";
|
||||
|
||||
$response .= "| Task | Type | Mon | Tue | Wed | Thu | Fri | Sat | Sun | Total |\n";
|
||||
$response .= "|------|------|-----|-----|-----|-----|-----|-----|-----|-------|\n";
|
||||
|
||||
$dailyTotals = [0, 0, 0, 0, 0, 0, 0];
|
||||
$grandTotal = 0;
|
||||
|
||||
foreach ($timesheetGroups as $group) {
|
||||
$row = '| '.($group['headline'] ?? 'No task').' | '.$group['kind'].' |';
|
||||
|
||||
for ($i = 1; $i <= 7; $i++) {
|
||||
$hours = $group["day{$i}"]['hours'] ?? 0;
|
||||
$row .= ' '.($hours > 0 ? number_format($hours, 1) : '-').' |';
|
||||
$dailyTotals[$i - 1] += $hours;
|
||||
}
|
||||
|
||||
$row .= ' '.number_format($group['rowSum'], 1).' |';
|
||||
$grandTotal += $group['rowSum'];
|
||||
|
||||
$response .= $row."\n";
|
||||
}
|
||||
|
||||
$response .= '| **Daily Totals** | | ';
|
||||
foreach ($dailyTotals as $total) {
|
||||
$response .= '**'.number_format($total, 1).'** | ';
|
||||
}
|
||||
$response .= '**'.number_format($grandTotal, 1)."** |\n\n";
|
||||
|
||||
return ToolResult::text($response);
|
||||
}
|
||||
}
|
||||
73
app/Domain/Timesheets/Tools/LogTimeTool.php
Normal file
73
app/Domain/Timesheets/Tools/LogTimeTool.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Log time for a specific ticket.
|
||||
*/
|
||||
class LogTimeTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->integer('ticketId')->description('ID of the ticket to log time for.')
|
||||
->required()
|
||||
->number('hours')->description('Number of hours to log.')
|
||||
->required()
|
||||
->string('date')->description('Date for the time entry in ISO8601 format.')
|
||||
->required()
|
||||
->string('kind')->description('Type of work (e.g., GENERAL_BILLABLE, DEVELOPMENT).')
|
||||
->required()
|
||||
->string('description')->description('Description of the work performed.');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'logTime';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Logs time for a specific ticket.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
try {
|
||||
$ticketId = (int) ($arguments['ticketId'] ?? 0);
|
||||
$params = [
|
||||
'date' => $arguments['date'],
|
||||
'hours' => ($arguments['hours'] ?? null),
|
||||
'kind' => $arguments['kind'],
|
||||
'description' => ($arguments['description'] ?? ''),
|
||||
];
|
||||
|
||||
$result = $this->timesheetsService->logTime($ticketId, $params);
|
||||
|
||||
if ($result) {
|
||||
$hours = ($arguments['hours'] ?? null);
|
||||
|
||||
return ToolResult::text("Time entry successfully logged: {$hours} hours on ticket #{$ticketId}.");
|
||||
}
|
||||
|
||||
return ToolResult::error('Failed to log time entry. Please check the provided information.');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error logging time: '.$e->getMessage());
|
||||
|
||||
return ToolResult::error('Failed to log time entry. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
67
app/Domain/Timesheets/Tools/StartTimerTool.php
Normal file
67
app/Domain/Timesheets/Tools/StartTimerTool.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Start a timer for a specific duration.
|
||||
*/
|
||||
class StartTimerTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->string('duration')->description('Duration in format like "25m" or "1h30m".')
|
||||
->required()
|
||||
->integer('taskId')->description('Task ID to associate timer with.')
|
||||
->string('type')
|
||||
->description('Timer type (work/break).');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'startTimer';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Start a timer for a specific duration.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
$duration = $arguments['duration'];
|
||||
$taskId = ($arguments['taskId'] ?? null);
|
||||
$type = ($arguments['type'] ?? 'work');
|
||||
|
||||
preg_match('/^(?:(\d+)h)?(?:(\d+)m)?$/', $duration, $matches);
|
||||
$minutes = 0;
|
||||
if (! empty($matches[1])) {
|
||||
$minutes += intval($matches[1]) * 60;
|
||||
}
|
||||
if (! empty($matches[2])) {
|
||||
$minutes += intval($matches[2]);
|
||||
}
|
||||
|
||||
if ($minutes <= 0) {
|
||||
return ToolResult::error('Invalid duration format. Use format like "25m" or "1h30m".');
|
||||
}
|
||||
|
||||
if ($taskId !== null && (int) $taskId > 0) {
|
||||
$this->timesheetsService->punchIn((int) $taskId);
|
||||
}
|
||||
|
||||
return ToolResult::text("[timer startTime='".(time() + 10)."' duration='".($minutes * 60)."' type='".$type."']");
|
||||
}
|
||||
}
|
||||
50
app/Domain/Timesheets/Tools/StopTimerTool.php
Normal file
50
app/Domain/Timesheets/Tools/StopTimerTool.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Timesheets\Tools;
|
||||
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Tools\ToolInputSchema;
|
||||
use Laravel\Mcp\Server\Tools\ToolResult;
|
||||
use Leantime\Domain\Timesheets\Services\Timesheets;
|
||||
|
||||
/**
|
||||
* Stop a running timer.
|
||||
*/
|
||||
class StopTimerTool extends Tool
|
||||
{
|
||||
public function __construct(
|
||||
private Timesheets $timesheetsService,
|
||||
) {}
|
||||
|
||||
public function schema(ToolInputSchema $schema): ToolInputSchema
|
||||
{
|
||||
return $schema
|
||||
->integer('ticketId')->description('Ticket ID to stop the timer for. Omit to stop the active timer.');
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return 'stopTimer';
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'Stop a running timer.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(array $arguments): ToolResult
|
||||
{
|
||||
$ticketId = ($arguments['ticketId'] ?? null);
|
||||
|
||||
if ($ticketId !== null && (int) $ticketId > 0) {
|
||||
$this->timesheetsService->punchOut((int) $ticketId);
|
||||
} else {
|
||||
$this->timesheetsService->stopActiveTimer();
|
||||
}
|
||||
|
||||
return ToolResult::text('Timer stopped successfully.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user