Ajoute created_by sur IssueIndexEntry/TicketSummaryDto + filtre createdBy (#5), et les use cases BulkUpdateIssueStatus/Priority + BulkDeleteIssues avec les DTO/outils MCP idea_ticket_bulk_* associés (#6). NON VERT : BulkDeleteIssues::execute référence BatchIssueResult::deleted(), constructeur pas encore ajouté à BatchIssueResult (seuls ok/err existent) -> `cargo check` échoue (E0599 dans application::issues::mod). Reste à DevBackend avant toute QA/merge. Branché sur feature/sdk-integration (dépend du commit #2 dans orchestrator/mcp/server.rs et tools.rs) ; à rebaser sur develop une fois feature/sdk-integration mergé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -328,6 +328,35 @@ pub struct UpdateIssueOutput {
|
||||
pub issue: Issue,
|
||||
}
|
||||
|
||||
/// Per-issue result for batch issue mutations.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BatchIssueResult {
|
||||
/// Requested issue reference.
|
||||
pub issue_ref: IssueRef,
|
||||
/// Updated issue for successful update operations.
|
||||
pub issue: Option<Issue>,
|
||||
/// Error for failed items.
|
||||
pub error: Option<AppError>,
|
||||
}
|
||||
|
||||
impl BatchIssueResult {
|
||||
fn ok(issue: Issue) -> Self {
|
||||
Self {
|
||||
issue_ref: issue.reference(),
|
||||
issue: Some(issue),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn err(issue_ref: IssueRef, error: AppError) -> Self {
|
||||
Self {
|
||||
issue_ref,
|
||||
issue: None,
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates issue fields with optimistic concurrency.
|
||||
pub struct UpdateIssue {
|
||||
issues: Arc<dyn IssueStore>,
|
||||
@ -402,6 +431,220 @@ impl UpdateIssue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`BulkUpdateIssueStatus::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BulkUpdateIssueStatusInput {
|
||||
/// Project owning the issues.
|
||||
pub project: Project,
|
||||
/// Issue references in requested order.
|
||||
pub issue_refs: Vec<IssueRef>,
|
||||
/// Replacement status.
|
||||
pub status: IssueStatus,
|
||||
/// Actor updating the issues.
|
||||
pub actor: IssueActor,
|
||||
}
|
||||
|
||||
/// Output for batch issue mutations.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BulkIssueMutationOutput {
|
||||
/// Per-reference results in input order.
|
||||
pub items: Vec<BatchIssueResult>,
|
||||
}
|
||||
|
||||
/// Updates several issue statuses, preserving input order and allowing partial success.
|
||||
pub struct BulkUpdateIssueStatus {
|
||||
issues: Arc<dyn IssueStore>,
|
||||
clock: Arc<dyn Clock>,
|
||||
events: Arc<dyn EventBus>,
|
||||
}
|
||||
|
||||
impl BulkUpdateIssueStatus {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
issues: Arc<dyn IssueStore>,
|
||||
clock: Arc<dyn Clock>,
|
||||
events: Arc<dyn EventBus>,
|
||||
) -> Self {
|
||||
Self {
|
||||
issues,
|
||||
clock,
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the batch update.
|
||||
pub async fn execute(&self, input: BulkUpdateIssueStatusInput) -> BulkIssueMutationOutput {
|
||||
let mut items = Vec::with_capacity(input.issue_refs.len());
|
||||
for issue_ref in input.issue_refs {
|
||||
items.push(
|
||||
update_issue_batch_item(
|
||||
&self.issues,
|
||||
&self.clock,
|
||||
&self.events,
|
||||
&input.project,
|
||||
issue_ref,
|
||||
input.actor.clone(),
|
||||
|issue| issue.status = input.status,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
BulkIssueMutationOutput { items }
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`BulkUpdateIssuePriority::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BulkUpdateIssuePriorityInput {
|
||||
/// Project owning the issues.
|
||||
pub project: Project,
|
||||
/// Issue references in requested order.
|
||||
pub issue_refs: Vec<IssueRef>,
|
||||
/// Replacement priority.
|
||||
pub priority: IssuePriority,
|
||||
/// Actor updating the issues.
|
||||
pub actor: IssueActor,
|
||||
}
|
||||
|
||||
/// Updates several issue priorities, preserving input order and allowing partial success.
|
||||
pub struct BulkUpdateIssuePriority {
|
||||
issues: Arc<dyn IssueStore>,
|
||||
clock: Arc<dyn Clock>,
|
||||
events: Arc<dyn EventBus>,
|
||||
}
|
||||
|
||||
impl BulkUpdateIssuePriority {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
issues: Arc<dyn IssueStore>,
|
||||
clock: Arc<dyn Clock>,
|
||||
events: Arc<dyn EventBus>,
|
||||
) -> Self {
|
||||
Self {
|
||||
issues,
|
||||
clock,
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the batch update.
|
||||
pub async fn execute(&self, input: BulkUpdateIssuePriorityInput) -> BulkIssueMutationOutput {
|
||||
let mut items = Vec::with_capacity(input.issue_refs.len());
|
||||
for issue_ref in input.issue_refs {
|
||||
items.push(
|
||||
update_issue_batch_item(
|
||||
&self.issues,
|
||||
&self.clock,
|
||||
&self.events,
|
||||
&input.project,
|
||||
issue_ref,
|
||||
input.actor.clone(),
|
||||
|issue| issue.priority = input.priority,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
BulkIssueMutationOutput { items }
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`BulkDeleteIssues::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BulkDeleteIssuesInput {
|
||||
/// Project owning the issues.
|
||||
pub project: Project,
|
||||
/// Issue references in requested order.
|
||||
pub issue_refs: Vec<IssueRef>,
|
||||
}
|
||||
|
||||
/// Deletes several issues, preserving input order and allowing partial success.
|
||||
pub struct BulkDeleteIssues {
|
||||
issues: Arc<dyn IssueStore>,
|
||||
events: Arc<dyn EventBus>,
|
||||
}
|
||||
|
||||
impl BulkDeleteIssues {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(issues: Arc<dyn IssueStore>, events: Arc<dyn EventBus>) -> Self {
|
||||
Self { issues, events }
|
||||
}
|
||||
|
||||
/// Executes the batch delete.
|
||||
pub async fn execute(&self, input: BulkDeleteIssuesInput) -> BulkIssueMutationOutput {
|
||||
let mut items = Vec::with_capacity(input.issue_refs.len());
|
||||
for issue_ref in input.issue_refs {
|
||||
let item = match self
|
||||
.issues
|
||||
.get_by_ref(&input.project.root, issue_ref)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
{
|
||||
Ok(issue) => {
|
||||
let freed_sprint = issue.sprint;
|
||||
match self.issues.delete(&input.project.root, issue_ref).await {
|
||||
Ok(()) => {
|
||||
self.events.publish(DomainEvent::IssueDeleted {
|
||||
project_id: input.project.id,
|
||||
issue_ref,
|
||||
freed_sprint,
|
||||
});
|
||||
BatchIssueResult::deleted(issue_ref)
|
||||
}
|
||||
Err(err) => BatchIssueResult::err(issue_ref, AppError::from(err)),
|
||||
}
|
||||
}
|
||||
Err(err) => BatchIssueResult::err(issue_ref, err),
|
||||
};
|
||||
items.push(item);
|
||||
}
|
||||
BulkIssueMutationOutput { items }
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_issue_batch_item(
|
||||
issues: &Arc<dyn IssueStore>,
|
||||
clock: &Arc<dyn Clock>,
|
||||
events: &Arc<dyn EventBus>,
|
||||
project: &Project,
|
||||
issue_ref: IssueRef,
|
||||
actor: IssueActor,
|
||||
mutate: impl FnOnce(&mut Issue),
|
||||
) -> BatchIssueResult {
|
||||
let current = match issues
|
||||
.get_by_ref(&project.root, issue_ref)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
{
|
||||
Ok(issue) => issue,
|
||||
Err(err) => return BatchIssueResult::err(issue_ref, err),
|
||||
};
|
||||
let old_status = current.status;
|
||||
let old_priority = current.priority;
|
||||
let old_assigned = assigned_set(¤t);
|
||||
let expected_version = current.version;
|
||||
let updated = match current
|
||||
.mutate(actor, now(clock), mutate)
|
||||
.map_err(|err| AppError::Invalid(err.to_string()))
|
||||
{
|
||||
Ok(issue) => issue,
|
||||
Err(err) => return BatchIssueResult::err(issue_ref, err),
|
||||
};
|
||||
match issues
|
||||
.update(&project.root, &updated, expected_version)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
{
|
||||
Ok(()) => {
|
||||
publish_update_events(events, &updated, old_status, old_priority, old_assigned);
|
||||
BatchIssueResult::ok(updated)
|
||||
}
|
||||
Err(err) => BatchIssueResult::err(issue_ref, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`UpdateIssueCarnet::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateIssueCarnetInput {
|
||||
|
||||
Reference in New Issue
Block a user