112 lines
3.8 KiB
PHP
112 lines
3.8 KiB
PHP
<?php
|
||
|
||
namespace Leantime\Domain\Tickets\Repositories;
|
||
|
||
use Illuminate\Database\ConnectionInterface;
|
||
use Leantime\Core\Db\Db as DbCore;
|
||
use Leantime\Core\Support\EntityRelationshipEnum;
|
||
|
||
/**
|
||
* 待办事项(ticket/milestone) ↔ 资源关联层。
|
||
*
|
||
* 复用多态表 zp_entity_relationship(零建表):
|
||
* entityA = ticket id(task 或 milestone,共用 zp_tickets.id 序列)
|
||
* entityAType = 'Ticket'
|
||
* entityB = 资源 id
|
||
* entityBType = 资源类型:bom | process | tooling | file | wiki
|
||
* relationship = EntityRelationshipEnum::LinkedResource->value
|
||
*
|
||
* 幂等在服务层事务 + lockForUpdate 实现(仿 Goalcanvas::addGoalMilestoneLink)。
|
||
*/
|
||
class TicketResource
|
||
{
|
||
private ConnectionInterface $db;
|
||
|
||
public function __construct(DbCore $db)
|
||
{
|
||
$this->db = $db->getConnection();
|
||
}
|
||
|
||
/**
|
||
* 关联一个资源(幂等:已存在则返回 true)。
|
||
*/
|
||
public function addLink(int $ticketId, string $resourceType, int $resourceId, int $userId): bool
|
||
{
|
||
if ($ticketId <= 0 || $resourceId <= 0 || $resourceType === '') {
|
||
return false;
|
||
}
|
||
|
||
return $this->db->transaction(function () use ($ticketId, $resourceType, $resourceId, $userId): bool {
|
||
$exists = $this->db->table('zp_entity_relationship')
|
||
->where('entityA', $ticketId)
|
||
->where('entityAType', 'Ticket')
|
||
->where('entityB', $resourceId)
|
||
->where('entityBType', $resourceType)
|
||
->where('relationship', EntityRelationshipEnum::LinkedResource->value)
|
||
->lockForUpdate()
|
||
->exists();
|
||
|
||
if ($exists) {
|
||
return true;
|
||
}
|
||
|
||
$this->db->table('zp_entity_relationship')->insert([
|
||
'entityA' => $ticketId,
|
||
'entityAType' => 'Ticket',
|
||
'entityB' => $resourceId,
|
||
'entityBType' => $resourceType,
|
||
'relationship' => EntityRelationshipEnum::LinkedResource->value,
|
||
'createdOn' => now(),
|
||
'createdBy' => $userId > 0 ? $userId : null,
|
||
]);
|
||
|
||
return true;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 解除单个资源关联。
|
||
*/
|
||
public function removeLink(int $ticketId, string $resourceType, int $resourceId): bool
|
||
{
|
||
return $this->db->table('zp_entity_relationship')
|
||
->where('entityA', $ticketId)
|
||
->where('entityAType', 'Ticket')
|
||
->where('entityB', $resourceId)
|
||
->where('entityBType', $resourceType)
|
||
->where('relationship', EntityRelationshipEnum::LinkedResource->value)
|
||
->delete() > 0;
|
||
}
|
||
|
||
/**
|
||
* 解除 ticket 的全部资源关联(删除 ticket 时级联清理)。
|
||
*/
|
||
public function removeAllLinks(int $ticketId): bool
|
||
{
|
||
return $this->db->table('zp_entity_relationship')
|
||
->where('entityA', $ticketId)
|
||
->where('entityAType', 'Ticket')
|
||
->where('relationship', EntityRelationshipEnum::LinkedResource->value)
|
||
->delete() > 0;
|
||
}
|
||
|
||
/**
|
||
* 某 ticket 已关联的资源(去重后的 [resourceType, resourceId] 列表)。
|
||
*
|
||
* @return array<int, array{type:string,id:int}>
|
||
*/
|
||
public function getLinks(int $ticketId): array
|
||
{
|
||
return $this->db->table('zp_entity_relationship')
|
||
->where('entityA', $ticketId)
|
||
->where('entityAType', 'Ticket')
|
||
->where('relationship', EntityRelationshipEnum::LinkedResource->value)
|
||
->orderBy('id')
|
||
->get(['entityBType', 'entityB'])
|
||
->map(fn ($row) => ['type' => (string) $row->entityBType, 'id' => (int) $row->entityB])
|
||
->unique(fn ($item) => $item['type'].':'.$item['id'])
|
||
->values()
|
||
->all();
|
||
}
|
||
}
|