import pytest from django.contrib.auth.models import Permission from django.urls import reverse from django.utils import timezone from playwright.sync_api import expect from apps.accounts.models import TicketMagicLink, User from apps.customers.models import ContactGroup, CustomerProfile from apps.messaging.models import Message from apps.notifications.models import NotificationJob, SMSTemplate from apps.staff.models import OrganizationalUnit, StaffProfile from apps.tickets.models import TicketCategory from apps.tickets.services import create_group_dispatch from tests.e2e.pages.auth import AuthenticationPage from tests.e2e.pages.dispatches import DispatchWorkspacePage pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)] PASSWORD = "Dispatch-E2E-safe-2026" @pytest.fixture def dispatch_case(): unit = OrganizationalUnit.objects.create(name="فروش رابط گروهی", slug="dispatch-ui") unit.permissions.add(*Permission.objects.filter(content_type__app_label="tickets", codename__in=("view_ticket", "change_ticket", "dispatch_group_tickets"))) staff = User.objects.create_user(username="dispatch-ui-agent", password=PASSWORD, account_type="STAFF") StaffProfile.objects.create(user=staff, display_name="کارشناس ارسال", primary_unit=unit) group = ContactGroup.objects.create(unit=unit, name="مخاطبان منتخب فروش") empty_group = ContactGroup.objects.create(unit=unit, name="گروه بدون مخاطب") category = TicketCategory.objects.create(unit=unit, title="هماهنگی فروش", slug="dispatch-ui-category") customers = [] for index, name in enumerate(("مخاطب پاسخ‌دهنده", "مخاطب بدون پاسخ")): customer = User.objects.create_user(username=f"dispatch-ui-customer-{index}", mobile=f"0912000980{index}", account_type="CUSTOMER") CustomerProfile.objects.create(user=customer, first_name=name).groups.add(group) customers.append(customer) return staff, unit, group, category, empty_group, customers @pytest.mark.parametrize("width", [1440, 390, 320]) def test_review_preserves_draft_and_requires_confirmation_before_group_send(live_server, browser_page, dispatch_case, width): staff, unit, group, category, _, _ = dispatch_case subject = "هماهنگی زمان تحویل گروهی" body = "لطفاً زمان پیشنهادی تحویل را در گفت‌وگوی خصوصی اعلام کنید." with browser_page() as page: page.set_viewport_size({"width": width, "height": 900 if width == 1440 else 844}) AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD) workspace = DispatchWorkspacePage(page, live_server.url) workspace.open_composer() workspace.compose(unit, group, category, subject, body) expect(page.get_by_label("خلاصه ارسال جدید")).to_contain_text("۲ نفر") workspace.open_history() expect(page.get_by_text("هنوز ارسال گروهی ثبت نشده است")).to_be_visible() workspace.return_to_composer() expect(page.get_by_label("متن پیام", exact=False)).to_have_value(body) workspace.review() dialog = page.get_by_role("dialog") expect(dialog).to_contain_text("۲ نفر") expect(dialog).to_contain_text(subject) workspace.cancel_review() expect(page.get_by_role("button", name="بررسی و ادامه")).to_be_enabled() workspace.save_screenshot(f"compose-{width}.png") workspace.review() workspace.save_screenshot(f"review-{width}.png") workspace.send() expect(page.locator("[data-recipient-row]")).to_have_count(2) expect(page.locator(".sms-state-card").first).to_contain_text("در صف ارسال") expect(page.get_by_text("تحویل پیامک به معنی مشاهده لینک یا دریافت پاسخ نیست.")).to_be_visible() workspace.expect_fits_viewport() @pytest.mark.parametrize("width", [1440, 390, 320]) def test_report_filters_search_and_private_reply_navigation(live_server, browser_page, dispatch_case, width): staff, unit, group, category, _, customers = dispatch_case dispatch, tickets = create_group_dispatch(user=staff, unit=unit, contact_group=group, category=category, subject="پیگیری پاسخ مخاطبان منتخب", body="پیام اصلی با پاسخ‌های خصوصی مستقل.", response_mode="INDIVIDUAL") first = tickets[0] Message.objects.create(ticket=first, author=customers[0], body="زمان پیشنهادی ما ساعت ده صبح است.") first.last_customer_message_at = timezone.now() first.save(update_fields=["last_customer_message_at"]) link = TicketMagicLink.issue_with_record(first, created_by=staff)[1] link.view_count = 2 link.save(update_fields=["view_count"]) NotificationJob.objects.filter(ticket=first, event_type=SMSTemplate.Event.STAFF_TICKET_CREATED).update(status="SENT", attempts=1, delivery_status="DELIVERED") NotificationJob.objects.filter(ticket=tickets[1], event_type=SMSTemplate.Event.STAFF_TICKET_CREATED).update(status="FAILED", attempts=1, last_error="خطای آزمایشی درگاه") with browser_page() as page: page.set_viewport_size({"width": width, "height": 900 if width == 1440 else 844}) AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD) workspace = DispatchWorkspacePage(page, live_server.url) workspace.open_report(dispatch) expect(workspace.visible_recipients()).to_have_count(2) workspace.filter_recipients("دارای پاسخ") expect(workspace.visible_recipients()).to_have_count(1) expect(workspace.visible_recipients()).to_contain_text("زمان پیشنهادی ما ساعت ده صبح است.") workspace.reset_recipients() workspace.filter_recipients("پیامک نیازمند اقدام") expect(workspace.visible_recipients()).to_have_count(1) expect(workspace.visible_recipients()).to_contain_text("ارسال ناموفق") # One failed attempt is not a successful first attempt. expect(workspace.visible_recipients()).not_to_contain_text("موفق در اولین تلاش") workspace.reset_recipients() workspace.search_recipient("۰۹۱۲۰۰۰۹۸۰۰") expect(workspace.visible_recipients()).to_have_count(1) workspace.search_recipient("وجود ندارد") expect(page.get_by_role("heading", name="مخاطبی با این شرایط پیدا نشد")).to_be_visible() workspace.reset_recipients() workspace.expect_fits_viewport() workspace.save_screenshot(f"report-{width}.png") workspace.filter_recipients("دارای پاسخ") workspace.open_conversation() expect(page).to_have_url(workspace.url(reverse("staff_tickets:detail", args=[first.public_id])))