fix sms and ticket user frindly

This commit is contained in:
javad committed 2026-09-14 15:48:13 +03:30
1 parent 1ee83374fd
commit b8b3aba855
30 files changed
+2220 -119

No files matched your search

+100 -3
View File
@@ -1,7 +1,11 @@
import base64
from datetime import timedelta
import pytest
from django.contrib.auth.models import Permission
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from django.utils import timezone
from apps.accounts.models import MagicLinkAccessLog, TicketMagicLink, User
from apps.attachments.models import AttachmentPolicy
@@ -157,6 +161,41 @@ def test_group_dispatch_copies_shared_document_into_each_private_ticket():
assert len(set(paths))==2
@pytest.mark.django_db
def test_dispatch_page_supports_professional_multi_attachment_picker_and_submission(client):
unit=OrganizationalUnit.objects.create(name="واحد پیوست گروهی",slug="dispatch-attachment-unit")
unit.permissions.add(Permission.objects.get(codename="dispatch_group_tickets",content_type__app_label="tickets"))
staff=make_staff("dispatch-attachment-staff",unit)
customer=make_contact("dispatch-attachment-customer","09120000313")
group=ContactGroup.objects.create(name="گروه پیوست",unit=unit)
customer.customer_profile.groups.add(group)
category=TicketCategory.objects.create(unit=unit,title="اسناد گروهی",slug="dispatch-attachments-category")
AttachmentPolicy.objects.update_or_create(attachment_type="IMAGE",defaults={"max_file_size":1024*1024,"max_files_per_message":10,"allowed_extensions":[".png"],"allowed_mime_types":["image/png"]})
AttachmentPolicy.objects.update_or_create(attachment_type="PDF",defaults={"max_file_size":1024*1024,"max_files_per_message":10,"allowed_extensions":[".pdf"],"allowed_mime_types":["application/pdf"]})
client.force_login(staff)
url=reverse("staff_tickets:dispatch_create")
page=client.get(url)
content=page.content.decode()
assert page.status_code==200
for marker in ("data-ticket-composer","data-gallery-input","data-document-input","data-file-preview","attachment-uploader.js"):
assert marker in content
assert "افزودن عکس از گالری" in content and "افزودن فایل" in content
response=client.post(url,{
"operation_type":GroupDispatch.OperationType.BULK,"unit":unit.pk,"contact_group":group.pk,
"category":category.pk,"subject":"ارسال چند پیوست گروهی","body":"تصویر و سند را بررسی کنید.",
"files":[
SimpleUploadedFile("preview.png",base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="),content_type="image/png"),
SimpleUploadedFile("document.pdf",b"%PDF-1.4\n%%EOF",content_type="application/pdf"),
],
})
assert response.status_code==302, response.context["form"].errors.as_json() if response.context else response.content.decode()
ticket=Ticket.objects.get(subject="ارسال چند پیوست گروهی")
assert set(ticket.ticket_messages.get().attachments.values_list("original_name",flat=True))=={"preview.png","document.pdf"}
@pytest.mark.django_db
def test_magic_link_records_every_open_with_ip_and_user_agent(client):
unit = OrganizationalUnit.objects.create(name="حسابداری ممیزی", slug="audit-accounting")
@@ -367,29 +406,87 @@ def test_unit_scoped_add_ticket_preserves_contact_ownership_and_stakeholder(clie
@pytest.mark.django_db
def test_dispatch_retry_is_unit_scoped_and_refreshes_persistent_status(client):
from apps.core.templatetags.jalali import jalali_datetime
unit=OrganizationalUnit.objects.create(name="لوجستیک retry",slug="logistics-retry")
other=OrganizationalUnit.objects.create(name="فروش retry",slug="sales-retry")
permission=Permission.objects.get(content_type__app_label="tickets",codename="dispatch_group_tickets")
unit.permissions.add(permission)
unit.permissions.add(permission,Permission.objects.get(content_type__app_label="tickets",codename="view_ticket"))
staff=make_staff("retry-dispatcher",unit); outsider=make_staff("retry-outsider",other)
contact=make_contact("retry-carrier","09120000302")
group=ContactGroup.objects.create(name="باربری retry",unit=unit); contact.customer_profile.groups.add(group)
category=TicketCategory.objects.create(title="استعلام retry",slug="quote-retry")
dispatch,tickets=dispatch_group_tickets(user=staff,unit=unit,contact_group=group,category=category,subject="استعلام هزینه جدید",body="قیمت را ثبت کنید.")
job=NotificationJob.objects.get(ticket=tickets[0],channel="SMS")
job.status=NotificationJob.Status.FAILED; job.attempts=3; job.last_error="provider"; job.save()
job.status=NotificationJob.Status.FAILED; job.attempts=4; job.last_error="0098SMS [کد -19]: شارژ پنل برای ارسال کافی نیست."; job.provider_status_code="-19"; job.save()
for _ in range(4):
TicketMagicLink.issue_with_record(tickets[0],reason="staff_created_ticket",created_by=staff)
# Supervisor alerts are separate from the recipient message and must not
# alter dispatch metrics or become the row's displayed/retryable SMS.
NotificationJob.objects.create(
channel=NotificationJob.Channel.SMS,event_type="UNIT_SUPERVISOR_NEW_TICKET",
ticket=tickets[0],customer=tickets[0].customer,payload={"recipient_mobile":"09120000999"},
status=NotificationJob.Status.SENT,attempts=1,next_attempt_at=timezone.now(),
dedupe_key=f"test-supervisor-{tickets[0].pk}",
)
refresh_group_dispatch_status(dispatch.pk); dispatch.refresh_from_db()
assert dispatch.status==GroupDispatch.Status.PARTIAL
client.force_login(staff)
detail=client.get(reverse("staff_tickets:dispatch_detail",args=[dispatch.public_id]))
content=detail.content.decode()
assert detail.status_code==200
assert "قیمت را ثبت کنید." in content
assert "متن اولیه کارشناس" in content
assert "کد -19" in content and "شارژ پنل" in content
assert "جزئیات فنی و رهگیری" in content
assert jalali_datetime(dispatch.created_at) in content
assert dispatch.created_at.strftime("%Y/%m/%d %H:%M") not in content
assert "0 + 0" not in content
assert "مشاهده نشده" in content
assert detail.context["tickets"][0].sms_job.pk==job.pk
assert detail.context["metrics"]["failed"]==1
ticket_detail=client.get(reverse("staff_tickets:detail",args=[tickets[0].public_id]))
assert ticket_detail.status_code==200
assert "علت دقیق" in ticket_detail.content.decode() and "کد -19" in ticket_detail.content.decode()
client.force_login(outsider)
assert client.post(reverse("staff_tickets:dispatch_retry",args=[dispatch.public_id])).status_code==403
client.force_login(staff)
assert client.post(reverse("staff_tickets:dispatch_retry",args=[dispatch.public_id])).status_code==302
assert client.post(reverse("staff_tickets:dispatch_retry",args=[dispatch.public_id]),{"job_id":job.pk}).status_code==302
job.refresh_from_db(); dispatch.refresh_from_db()
assert (job.status,job.attempts,job.last_error)==(NotificationJob.Status.PENDING,0,"")
assert job.provider_status_code=="" and job.delivery_status==""
assert dispatch.status==GroupDispatch.Status.QUEUED
@pytest.mark.django_db
def test_provisional_delivery_is_tracking_not_failure_or_manual_retry(client):
unit=OrganizationalUnit.objects.create(name="پیگیری دلیوری",slug="delivery-tracking-unit")
unit.permissions.add(*Permission.objects.filter(content_type__app_label="tickets",codename__in=("view_ticket","dispatch_group_tickets")))
staff=make_staff("delivery-tracking-agent",unit)
contact=make_contact("delivery-tracking-contact","09120000322")
group=ContactGroup.objects.create(name="گروه پیگیری",unit=unit); contact.customer_profile.groups.add(group)
category=TicketCategory.objects.create(unit=unit,title="پیگیری",slug="delivery-tracking-category")
dispatch,tickets=dispatch_group_tickets(user=staff,unit=unit,contact_group=group,category=category,subject="پیگیری تحویل",body="متن")
job=NotificationJob.objects.get(ticket=tickets[0],event_type="STAFF_TICKET_CREATED")
NotificationJob.objects.filter(pk=job.pk).update(
status=NotificationJob.Status.SENT,attempts=1,sent_at=timezone.now(),provider_message_id="123456789",
delivery_status=NotificationJob.DeliveryStatus.UNKNOWN,delivery_status_code="8",
delivery_next_check_at=timezone.now()+timedelta(minutes=3),delivery_check_attempts=1,
)
refresh_group_dispatch_status(dispatch.pk); dispatch.refresh_from_db()
assert dispatch.status==GroupDispatch.Status.SENT
client.force_login(staff)
detail=client.get(reverse("staff_tickets:dispatch_detail",args=[dispatch.public_id]))
assert detail.context["metrics"]["tracking"]==1 and detail.context["metrics"]["failed"]==0
assert detail.context["tickets"][0].sms_retryable is False
client.post(reverse("staff_tickets:dispatch_retry",args=[dispatch.public_id]),{"job_id":job.pk})
job.refresh_from_db()
assert job.status==NotificationJob.Status.SENT and job.delivery_status==NotificationJob.DeliveryStatus.UNKNOWN
@pytest.mark.django_db
def test_magic_link_can_be_revoked_only_with_unit_permission(client):
unit=OrganizationalUnit.objects.create(name="حسابرسی",slug="audit-unit")