def get_or_open(cls, org, user, message, summary, assignee): from casepro.profiles.models import Notification r = get_redis_connection() with r.lock(CASE_LOCK_KEY % (org.pk, message.contact.uuid)): message.refresh_from_db() # if message is already associated with a case, return that if message.case: message.case.is_new = False return message.case # suspend from groups, expire flows and archive messages message.contact.prepare_for_case() case = cls.objects.create(org=org, assignee=assignee, initial_message=message, contact=message.contact, summary=summary) case.is_new = True case.labels.add(*list(message.labels.all())) # copy labels from message to new case case.watchers.add(user) # attach message to this case message.case = case message.save(update_fields=('case',)) action = CaseAction.create(case, user, CaseAction.OPEN, assignee=assignee) for assignee_user in assignee.get_users(): if assignee_user != user: Notification.new_case_assignment(org, assignee_user, action) return case
def reassign(self, user, partner, note=None): from casepro.profiles.models import Notification self.assignee = partner self.save(update_fields=('assignee',)) action = CaseAction.create(self, user, CaseAction.REASSIGN, assignee=partner, note=note) self.notify_watchers(action=action) # also notify users in the assigned partner that this case has been assigned to them for user in partner.get_users(): Notification.new_case_assignment(self.org, user, action)
def reassign(self, user, partner, note=None, user_assignee=None): from casepro.profiles.models import Notification self.assignee = partner self.user_assignee = user_assignee self.save(update_fields=("assignee", "user_assignee")) action = CaseAction.create( self, user, CaseAction.REASSIGN, assignee=partner, note=note, user_assignee=user_assignee ) self.notify_watchers(action=action) # also notify users in the assigned partner that this case has been assigned to them for user in partner.get_users(): Notification.new_case_assignment(self.org, user, action)
def get_or_open(cls, org, user, message, summary, assignee, user_assignee=None, contact=None): """ Get an existing case, or open a new case if one doesn't exist. If message=None, then contact is required, and any open case for that contact will be returned. If no open cases exist for the contact, a new case will be created. """ if not message and not contact: raise ValueError("Opening a case requires a message or contact") from casepro.profiles.models import Notification r = get_redis_connection() contact = message.contact if message else contact with r.lock(CASE_LOCK_KEY % (org.pk, contact.uuid)): if message: message.refresh_from_db() case = message.case else: message = None case = contact.cases.filter(closed_on=None).first() # if there is already an associated case, return that if case: case.is_new = False return case # suspend from groups, expire flows and archive messages contact.prepare_for_case() case = cls.objects.create(org=org, assignee=assignee, initial_message=message, contact=contact, summary=summary, user_assignee=user_assignee) if message: case.labels.add(*list(message.labels.all())) # copy labels from message to new case # attach message to this case message.case = case message.save(update_fields=('case',)) case.is_new = True case.watchers.add(user) action = CaseAction.create(case, user, CaseAction.OPEN, assignee=assignee, user_assignee=user_assignee) for assignee_user in assignee.get_users(): if assignee_user != user: Notification.new_case_assignment(org, assignee_user, action) return case
class Case(models.Model): """ A case between a partner organization and a contact """ org = models.ForeignKey(Org, verbose_name=_("Organization"), related_name="cases", on_delete=models.PROTECT) labels = models.ManyToManyField(Label, help_text=_("Labels assigned to this case")) assignee = models.ForeignKey(Partner, related_name="cases", on_delete=models.PROTECT) user_assignee = models.ForeignKey( User, null=True, on_delete=models.SET_NULL, related_name="cases", help_text="The (optional) user that this case is assigned to", ) contact = models.ForeignKey(Contact, related_name="cases", on_delete=models.PROTECT) initial_message = models.OneToOneField(Message, null=True, related_name="initial_case", on_delete=models.PROTECT) summary = models.CharField(verbose_name=_("Summary"), max_length=255) opened_on = models.DateTimeField(auto_now_add=True, help_text="When this case was opened") closed_on = models.DateTimeField(null=True, help_text="When this case was closed") watchers = models.ManyToManyField( User, related_name="watched_cases", help_text="Users to be notified of case activity" ) @classmethod def get_all(cls, org, user=None, label=None): queryset = cls.objects.filter(org=org) if user: # if user is not an org admin, we should only return cases with partner labels or assignment user_partner = user.get_partner(org) if user_partner and user_partner.is_restricted: queryset = queryset.filter(Q(labels__in=list(user_partner.get_labels())) | Q(assignee=user_partner)) if label: queryset = queryset.filter(labels=label) return queryset.distinct() @classmethod def get_open(cls, org, user=None, label=None): return cls.get_all(org, user, label).filter(closed_on=None) @classmethod def get_closed(cls, org, user=None, label=None): return cls.get_all(org, user, label).exclude(closed_on=None) @classmethod def get_for_contact(cls, org, contact): return cls.get_all(org).filter(contact=contact) @classmethod def get_open_for_contact_on(cls, org, contact, dt): qs = cls.get_for_contact(org, contact) return qs.filter(opened_on__lt=dt).filter(Q(closed_on=None) | Q(closed_on__gt=dt)).first() @classmethod def search(cls, org, user, search): """ Search for cases """ folder = search.get("folder") assignee_id = search.get("assignee") after = search.get("after") before = search.get("before") if folder == CaseFolder.open: queryset = Case.get_open(org, user) elif folder == CaseFolder.closed: queryset = Case.get_closed(org, user) else: # pragma: no cover raise ValueError("Invalid folder for cases") if assignee_id: queryset = queryset.filter(assignee__pk=assignee_id) if after: queryset = queryset.filter(opened_on__gte=after) if before: queryset = queryset.filter(opened_on__lte=before) queryset = queryset.select_related("contact", "assignee", "user_assignee") queryset = queryset.prefetch_related(Prefetch("labels", Label.objects.filter(is_active=True))) return queryset.order_by("-opened_on") @classmethod def get_or_open(cls, org, user, message, summary, assignee, user_assignee=None, contact=None): """ Get an existing case, or open a new case if one doesn't exist. If message=None, then contact is required, and any open case for that contact will be returned. If no open cases exist for the contact, a new case will be created. """ if not message and not contact: raise ValueError("Opening a case requires a message or contact") from casepro.profiles.models import Notification r = get_redis_connection() contact = message.contact if message else contact with r.lock(CASE_LOCK_KEY % (org.pk, contact.uuid)): if message: message.refresh_from_db() case = message.case else: message = None case = contact.cases.filter(closed_on=None).first() # if there is already an associated case, return that if case: case.is_new = False return case # suspend from groups, expire flows and archive messages contact.prepare_for_case() case = cls.objects.create( org=org, assignee=assignee, initial_message=message, contact=contact, summary=summary, user_assignee=user_assignee, ) if message: case.labels.add(*list(message.labels.all())) # copy labels from message to new case # attach message and subsequent messages to this case contact.incoming_messages.filter(case=None, created_on__gte=message.created_on).update(case=case) case.is_new = True case.watchers.add(user) action = CaseAction.create(case, user, CaseAction.OPEN, assignee=assignee, user_assignee=user_assignee) for assignee_user in assignee.get_users(): if assignee_user != user: Notification.new_case_assignment(org, assignee_user, action)