OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Leantime\Core\Resources\Models;
/**
* A budget line — a chunk of money allocated to one project inside a program.
*
* `spent` is a committed value: providers that don't track actuals pass 0.0.
* We deliberately don't distinguish "unknown" from "zero" at the model level
* because that ambiguity would leak into every consumer (report tile, UI, RPC
* caller); providers that need to expose an "actuals unavailable" state
* should surface it via their own domain, not through a nullable here.
*/
final class BudgetLine
{
public function __construct(
public readonly int $itemId,
public readonly int $projectId,
public readonly string $label,
public readonly float $budgeted,
public readonly float $spent,
public readonly ?string $color,
) {}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Leantime\Core\Resources\Models;
/**
* A dependency — an external commitment a program needs to succeed
* (partnership, facility, grant, supplier, regulatory approval — things
* outside your direct control).
*
* `confirmed` distinguishes "we have this locked in" from "we're still
* counting on this happening" — the primary board-level risk signal.
*
* The four nullable trailing fields are optional context that surface in
* the stakeholder report's Dependencies section when populated. Older
* canvas items without these fields still hydrate cleanly (they just
* render without owner/due-date/notes annotations).
*/
final class Dependency
{
public function __construct(
public readonly int $itemId,
public readonly string $partnerName,
public readonly string $type,
public readonly bool $confirmed,
public readonly ?string $owner = null,
public readonly ?string $dueDate = null,
public readonly ?string $notes = null,
public readonly ?string $lastModified = null,
) {}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Leantime\Core\Resources\Models;
/**
* A person allocated across one or more projects.
*
* `allocations` is a per-project map: `[projectId => weeklyHours]`. The
* provider is responsible for keeping keys and hours in sync with its own
* canvas storage. Consumers only read.
*/
final class PersonAllocation
{
/**
* @param array<int, float> $allocations projectId => weekly hours
*/
public function __construct(
public readonly int $itemId,
public readonly ?int $userId,
public readonly string $displayName,
public readonly float $capacity,
public readonly array $allocations,
) {}
/**
* Weekly hours allocated across all projects.
*/
public function totalAllocated(): float
{
return array_sum($this->allocations);
}
/**
* Remaining weekly capacity (never negative). Over-allocation is a real
* product state — callers that want to render an "over-allocated" badge
* compare `totalAllocated()` against `capacity` directly.
*/
public function available(): float
{
return max(0.0, $this->capacity - $this->totalAllocated());
}
}

View File

@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Leantime\Core\Resources\Models;
/**
* The shape of a Resources aggregation, as seen by every consumer (report
* engine, UI section, JSON-RPC caller).
*
* Providers assemble this by reading their own storage and mapping into these
* value objects. Consumers depend only on this class — never on the provider's
* internal repository or canvas tables.
*
* Empty state ({@see self::empty()}) is a valid return, not an error. A
* consumer with no gateway registered gets null from the registry; a consumer
* with a gateway that has nothing authored gets an empty summary. Both branches
* are legitimate design states — the caller decides which affordance to
* render.
*/
final class ResourceSummary
{
/**
* @param int[] $projectIds The projects this summary aggregates over.
* @param array<int, PersonAllocation> $people
* @param array<int, BudgetLine> $budget
* @param array<int, Dependency> $dependencies
* @param float $totalCapacity Sum of `people[].capacity`.
* @param float $totalAllocated Sum of allocations across people.
* @param float $totalBudgeted Sum of `budget[].budgeted`.
* @param float $totalSpent Sum of `budget[].spent`.
* @param float $totalActual Hours actually logged (timesheets) across
* the project set in the CURRENT WEEK of the
* requesting user's timezone. Weekly window
* by design: capacity/allocated are weekly
* rates (h/wk), so the comparable actual is
* this week's logged hours. Period-scoped
* actuals (e.g. a report quarter) come from
* the report engine's own timesheet reads —
* not this summary.
* @param array<int, float> $actualsByProject projectId => hours logged
* this week. Same window as $totalActual.
*/
public function __construct(
public readonly array $projectIds,
public readonly array $people,
public readonly array $budget,
public readonly array $dependencies,
public readonly float $totalCapacity,
public readonly float $totalAllocated,
public readonly float $totalBudgeted,
public readonly float $totalSpent,
public readonly float $totalActual = 0.0,
public readonly array $actualsByProject = [],
) {}
/**
* Plan-vs-actual drift for the current week: negative = under plan
* (fewer hours logged than allocated), positive = over. Zero when
* nothing is allocated AND nothing is logged — callers should treat
* that case as "nothing to compare", not "on plan" (see the Resource
* Allocation tab's Gap-column semantics).
*/
public function actualDrift(): float
{
return $this->totalActual - $this->totalAllocated;
}
/**
* Empty summary — no resources authored across the given project set.
* Consumers should treat this as "the plugin is installed and answered,
* but there's nothing to show." Distinct from a null registry lookup.
*
* @param int[] $projectIds
*/
public static function empty(array $projectIds = []): self
{
return new self($projectIds, [], [], [], 0.0, 0.0, 0.0, 0.0);
}
/**
* True when the summary has no people, no budget, and no dependencies.
*/
public function isEmpty(): bool
{
return $this->people === [] && $this->budget === [] && $this->dependencies === [];
}
/**
* Capacity utilization as a 0-100 percentage. Returns 0 when no capacity
* has been declared to avoid divide-by-zero in report tiles.
*/
public function capacityUtilization(): float
{
if ($this->totalCapacity <= 0) {
return 0.0;
}
return round(($this->totalAllocated / $this->totalCapacity) * 100, 1);
}
/**
* Budget utilization as a 0-100 percentage. Returns 0 when no budget has
* been declared.
*/
public function budgetUtilization(): float
{
if ($this->totalBudgeted <= 0) {
return 0.0;
}
return round(($this->totalSpent / $this->totalBudgeted) * 100, 1);
}
}