@@ -4,11 +4,13 @@ import (
44 "context"
55 "fmt"
66 "io"
7+ "maps"
78 "mime/multipart"
89 "slices"
910 "strings"
1011 "time"
1112
13+ "github.com/checkmarble/marble-backend/dto"
1214 "github.com/checkmarble/marble-backend/models"
1315 "github.com/checkmarble/marble-backend/pure_utils"
1416 "github.com/checkmarble/marble-backend/repositories"
@@ -69,6 +71,10 @@ type CaseUseCaseRepository interface {
6971 GetNextCase (ctx context.Context , exec repositories.Executor , c models.Case ) (string , error )
7072
7173 UserById (ctx context.Context , exec repositories.Executor , userId string ) (models.User , error )
74+
75+ CaseMassChangeStatus (ctx context.Context , tx repositories.Transaction , caseIds []uuid.UUID , status models.CaseStatus ) ([]uuid.UUID , error )
76+ CaseMassAssign (ctx context.Context , tx repositories.Transaction , caseIds []uuid.UUID , assigneeId uuid.UUID ) ([]uuid.UUID , error )
77+ CaseMassMoveToInbox (ctx context.Context , tx repositories.Transaction , caseIds []uuid.UUID , inboxId uuid.UUID ) ([]uuid.UUID , error )
7278}
7379
7480type CaseUsecaseScreeningRepository interface {
@@ -1663,3 +1669,141 @@ func (uc *CaseUseCase) triggerAutoAssignment(ctx context.Context, tx repositorie
16631669
16641670 return nil
16651671}
1672+
1673+ func (uc * CaseUseCase ) MassUpdate (ctx context.Context , req dto.CaseMassUpdateDto ) error {
1674+ exec := uc .executorFactory .NewExecutor ()
1675+ orgId := uc .enforceSecurity .OrgId ()
1676+ userId := uc .enforceSecurity .UserId ()
1677+
1678+ sourceCases := make (map [string ]models.Case , len (req .CaseIds ))
1679+ events := make (map [string ]models.CreateCaseEventAttributes , len (req .CaseIds ))
1680+
1681+ var newAssignee models.User
1682+
1683+ availableInboxIds , err := uc .getAvailableInboxIds (ctx , exec , orgId )
1684+ if err != nil {
1685+ return err
1686+ }
1687+
1688+ if req .Action == "assign" {
1689+ var err error
1690+
1691+ newAssignee , err = uc .repository .UserById (ctx , exec , req .Assign .AssigneeId .String ())
1692+ if err != nil {
1693+ return errors .Wrap (err , "target user for assignment not found" )
1694+ }
1695+ }
1696+
1697+ // For all cases in the mass update, we need to check the current user can manage them.
1698+ for _ , caseId := range req .CaseIds {
1699+ // TODO: that is harsh, refactor GetCaseById into a single SQL query to
1700+ // retrieve all cases outside of the loop
1701+ c , err := uc .repository .GetCaseById (ctx , exec , caseId .String ())
1702+ if err != nil {
1703+ return err
1704+ }
1705+
1706+ if err := uc .enforceSecurity .ReadOrUpdateCase (c .GetMetadata (), availableInboxIds ); err != nil {
1707+ return err
1708+ }
1709+
1710+ // If we are trying to mass-assign, we need to check, for each case, that the target user can manage the case.
1711+ if req .Action == "assign" {
1712+ if err := security .EnforceSecurityCaseForUser (newAssignee ).ReadOrUpdateCase (c .GetMetadata (), availableInboxIds ); err != nil {
1713+ return errors .Wrap (err , "target user lacks case permissions for assignment" )
1714+ }
1715+ }
1716+
1717+ sourceCases [c .Id ] = c
1718+ }
1719+
1720+ // When changing the cases' inboxes, the user needs to have access to the target inbox.
1721+ if req .Action == "move_to_inbox" {
1722+ if _ , err := uc .inboxReader .GetInboxById (ctx , req .MoveToInbox .InboxId ); err != nil {
1723+ return errors .Wrap (err , fmt .Sprintf ("User does not have access the new inbox %s" , req .MoveToInbox .InboxId ))
1724+ }
1725+ }
1726+
1727+ return uc .transactionFactory .Transaction (ctx , func (tx repositories.Transaction ) error {
1728+ switch req .Action {
1729+ case "close" :
1730+ updatedIds , err := uc .repository .CaseMassChangeStatus (ctx , tx , req .CaseIds , models .CaseClosed )
1731+
1732+ if err != nil {
1733+ return err
1734+ }
1735+
1736+ for _ , updatedId := range updatedIds {
1737+ events [updatedId .String ()] = models.CreateCaseEventAttributes {
1738+ UserId : userId ,
1739+ CaseId : updatedId .String (),
1740+ EventType : models .CaseStatusUpdated ,
1741+ PreviousValue : utils .Ptr (string (sourceCases [updatedId .String ()].Status )),
1742+ NewValue : utils .Ptr (string (models .CaseClosed )),
1743+ }
1744+ }
1745+
1746+ case "reopen" :
1747+ updatedIds , err := uc .repository .CaseMassChangeStatus (ctx , tx , req .CaseIds , models .CasePending )
1748+
1749+ if err != nil {
1750+ return err
1751+ }
1752+
1753+ for _ , updatedId := range updatedIds {
1754+ events [updatedId .String ()] = models.CreateCaseEventAttributes {
1755+ UserId : userId ,
1756+ CaseId : updatedId .String (),
1757+ EventType : models .CaseStatusUpdated ,
1758+ PreviousValue : utils .Ptr (string (sourceCases [updatedId .String ()].Status )),
1759+ NewValue : utils .Ptr (string (models .CasePending )),
1760+ }
1761+ }
1762+
1763+ case "assign" :
1764+ updatedIds , err := uc .repository .CaseMassAssign (ctx , tx , req .CaseIds , req .Assign .AssigneeId )
1765+
1766+ if err != nil {
1767+ return err
1768+ }
1769+
1770+ for _ , updatedId := range updatedIds {
1771+ events [updatedId .String ()] = models.CreateCaseEventAttributes {
1772+ UserId : userId ,
1773+ CaseId : updatedId .String (),
1774+ EventType : models .CaseAssigned ,
1775+ PreviousValue : (* string )(sourceCases [updatedId .String ()].AssignedTo ),
1776+ NewValue : utils .Ptr (req .Assign .AssigneeId .String ()),
1777+ }
1778+ }
1779+
1780+ case "move_to_inbox" :
1781+ updatedIds , err := uc .repository .CaseMassMoveToInbox (ctx , tx , req .CaseIds , req .MoveToInbox .InboxId )
1782+
1783+ if err != nil {
1784+ return err
1785+ }
1786+
1787+ for _ , updatedId := range updatedIds {
1788+ events [updatedId .String ()] = models.CreateCaseEventAttributes {
1789+ UserId : userId ,
1790+ CaseId : updatedId .String (),
1791+ EventType : models .CaseInboxChanged ,
1792+ PreviousValue : utils .Ptr (sourceCases [updatedId .String ()].InboxId .String ()),
1793+ NewValue : utils .Ptr (req .MoveToInbox .InboxId .String ()),
1794+ }
1795+ }
1796+
1797+ }
1798+
1799+ if len (events ) == 0 {
1800+ return nil
1801+ }
1802+
1803+ if err := uc .repository .BatchCreateCaseEvents (ctx , tx , slices .Collect (maps .Values (events ))); err != nil {
1804+ return err
1805+ }
1806+
1807+ return nil
1808+ })
1809+ }
0 commit comments