add payment_link and banks

This commit is contained in:
javad committed 2026-10-06 10:03:22 +03:30
1 parent 2ae79b1ca4
commit de3135b588
100 files changed
+6167 -47

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ class AuthenticationPage(BasePage):
self.page.locator('input[name="mobile"]').fill(customer.local_mobile)
self.page.locator('input[name="password"]').fill(password)
self.page.get_by_role("button", name="ورود به حساب").click()
expect(self.page).to_have_url(self.url(reverse("tickets:list")))
expect(self.page).to_have_url(self.url(reverse("payments:home")))
def login_staff(self, staff, password):
self.goto(reverse("accounts:staff_login"))
+44
View File
@@ -0,0 +1,44 @@
from django.urls import reverse
from playwright.sync_api import expect
from tests.e2e.pages.base import BasePage
class MagicLinkConfirmationPage(BasePage):
def open(self, token):
self.goto(reverse("accounts:magic", args=[token]))
expect(self.page.get_by_role("heading", name="این لینک مربوط به حساب دیگری است")).to_be_visible()
@property
def continue_button(self):
return self.page.get_by_role("button", name="خروج از حساب و مشاهده تیکت", exact=True)
@property
def cancel_link(self):
return self.page.get_by_role("link", name="انصراف از مشاهده این تیکت/پیام", exact=True)
def continue_to_ticket(self, ticket):
self.continue_button.click()
expect(self.page).to_have_url(self.url(reverse("tickets:detail", args=[ticket.public_id])))
expect(self.page.get_by_role("heading", name=ticket.subject, exact=True)).to_be_visible()
expect(self.page.get_by_text("فقط به همین تیکت دسترسی دارید", exact=False)).to_be_visible()
def cancel(self):
self.cancel_link.click()
expect(self.page).to_have_url(self.url(reverse("payments:home")))
def expect_readable_layout(self):
expect(self.continue_button).to_be_visible()
expect(self.cancel_link).to_be_visible()
metrics = self.page.evaluate("""() => ({
width: document.documentElement.clientWidth,
scrollWidth: document.documentElement.scrollWidth,
textSize: parseFloat(getComputedStyle(document.querySelector('#confirmation-description')).fontSize),
background: getComputedStyle(document.querySelector('.magic-confirmation-primary')).backgroundColor,
color: getComputedStyle(document.querySelector('.magic-confirmation-primary')).color,
})""")
assert metrics["scrollWidth"] <= metrics["width"]
assert metrics["textSize"] >= 16
assert metrics["background"] == "rgb(8, 55, 107)" and metrics["color"] == "rgb(255, 255, 255)"
for control in (self.continue_button, self.cancel_link):
assert control.bounding_box()["height"] >= 44
+40
View File
@@ -0,0 +1,40 @@
from playwright.sync_api import expect
from .payments import PaymentsPage
class PaymentLinkPage(PaymentsPage):
def open_link(self, path):
self.goto(path)
expect(self.page.get_by_role('heading', name='جزئیات واریز', exact=True)).to_be_visible()
def copy_all(self):
self.page.get_by_role('button', name='کپی کامل اطلاعات', exact=True).click()
expect(self.page.get_by_text('کپی شد', exact=True).or_(
self.page.get_by_role('heading', name='متن را انتخاب و کپی کنید'))).to_be_visible()
if self.page.get_by_role('dialog').is_visible():
self.page.get_by_role('button', name='بستن', exact=True).click()
def open_receipt_form(self, linked):
if linked:
self.page.get_by_role('link', name='ارسال فیش این واریز', exact=True).click()
else:
self.page.get_by_role('link', name='ارسال فیش واریز مستقل', exact=True).click()
expect(self.page.get_by_role('heading', name='ارسال فیش واریزی', exact=True)).to_be_visible()
def show_required_errors(self):
self.page.get_by_role('button', name='ثبت و ارسال فیش', exact=True).click()
expect(self.page.locator('#receipt-errors')).to_be_visible()
expect(self.page.locator('#id_paid_amount')).to_be_focused()
def submit_payment(self, png, account_id=None):
if account_id is not None:
self.page.locator('#id_destination_account').select_option(str(account_id))
self.page.get_by_label('شناسه واریز که با آن پرداخت انجام داده‌اید (اختیاری)', exact=True).fill('')
expect(self.page.locator('#id_deposit_id')).not_to_have_attribute('required', '')
self.page.locator('#id_paid_amount').fill('۱۲۳۴۵')
expect(self.page.locator('#id_paid_amount')).to_have_value('۱۲٬۳۴۵')
self.page.get_by_role('button', name='امروز', exact=True).click()
self.page.locator('#id_receipt_image').set_input_files({'name': 'payment.png', 'mimeType': 'image/png', 'buffer': png})
self.page.get_by_role('button', name='ثبت و ارسال فیش', exact=True).click()
expect(self.page.locator('.flash')).to_contain_text('فیش ثبت شد و در انتظار بررسی است.')
+33
View File
@@ -0,0 +1,33 @@
from django.urls import reverse
from playwright.sync_api import expect
from .base import BasePage
class PaymentsPage(BasePage):
def select_customer(self, mobile):
self.page.get_by_label('جست‌وجو و انتخاب مشتری', exact=False).fill(mobile)
result = self.page.locator('.pay-customer-result').filter(has_text=mobile)
result.get_by_role('button', name='انتخاب این مشتری', exact=True).click()
expect(self.page.locator('[data-selected-customer]')).to_contain_text(mobile)
def open_hub(self):
self.goto(reverse('payments:home'))
expect(self.page.get_by_role('heading',name='چهارسو',exact=True)).to_be_visible()
def send_receipt(self, account_id, png, linked=False):
self.goto(reverse('payments:receipt_new'))
if not linked:
self.page.get_by_label('واریز مستقل',exact=True).check()
self.page.locator('#id_paid_amount').fill('۱۲۳۴۵')
self.page.locator('#id_destination_account').select_option(str(account_id))
self.page.get_by_role('button',name='امروز',exact=True).click()
self.page.locator('#id_receipt_image').set_input_files({'name':'receipt.png','mimeType':'image/png','buffer':png})
self.page.get_by_role('button',name='ثبت و ارسال فیش').click()
expect(self.page.locator('.flash')).to_contain_text('فیش ثبت شد و در انتظار بررسی است.')
def approve(self, receipt_id, senior=False):
self.goto(reverse('staff_payments:receipt_detail',args=[receipt_id]))
if senior:
self.page.locator('#decision-reason').fill('تطبیق پرداخت بررسی شد')
self.page.get_by_role('button',name='تأیید نهایی ارشد' if senior else 'ثبت تأیید من',exact=True).click()
expect(self.page.locator('.flash')).to_contain_text('تصمیم ثبت شد.')
+26
View File
@@ -0,0 +1,26 @@
from playwright.sync_api import expect
from .base import BasePage
class TicketNoticesPage(BasePage):
def open_customer_panel(self):
if self.page.get_by_role('button', name='اطلاعات مشتری', exact=True).is_visible():
self.page.get_by_role('button', name='اطلاعات مشتری', exact=True).click()
expect(self.page.locator('#customer-info')).to_be_in_viewport()
def toggle_notices(self):
self.page.locator('.ticket-notices > summary').click()
def open_notice(self, subject):
self.page.get_by_role('link').filter(has=self.page.get_by_text(subject, exact=True)).click()
def open_all(self):
self.page.get_by_role('link', name='بیشتر همه اعلامیه‌های این مشتری').click()
def expect_closed(self):
expect(self.page.get_by_role('link', name='ایجاد اعلامیه واریز', exact=True)).to_be_hidden()
def expect_open(self):
expect(self.page.get_by_role('link', name='ایجاد اعلامیه واریز', exact=True)).to_be_visible()
expect(self.page.get_by_role('list', name='اعلامیه‌های اخیر مشتری').get_by_role('link')).to_have_count(3)
+1 -1
View File
@@ -26,7 +26,7 @@ def test_external_customer_login_and_public_routing_are_usable(
page.locator('input[name="mobile"]').fill(mobile)
page.locator('input[name="password"]').fill(password)
page.get_by_role("button", name="ورود به حساب").click()
expect(page).to_have_url(f"{application_base_url}/tickets/")
expect(page).to_have_url(f"{application_base_url}/chaharsu/")
page.goto(f"{application_base_url}/tickets/new/", wait_until="domcontentloaded")
expect(page.locator("[data-ticket-unit]")).to_be_visible()
expect(page.locator("[data-ticket-category]")).to_be_visible()
@@ -0,0 +1,75 @@
from pathlib import Path
import pytest
from django.urls import reverse
from playwright.sync_api import expect
from apps.accounts.models import TicketMagicLink, User
from apps.customers.models import CustomerProfile
from apps.tickets.models import TicketCategory
from apps.tickets.services import create_ticket
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.magic_link import MagicLinkConfirmationPage
from tests.e2e.pages.tickets import TicketConversationPage
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
PASSWORD = "Magic-link-test-password-2026"
MESSAGE = "شما با حساب دیگری وارد سامانه شده‌اید. برای مشاهده تیکت مرتبط با این لینک، از حساب فعلی خارج شوید. پس از خروج، تیکت به‌صورت خودکار باز می‌شود و نیازی به ورود مجدد ندارید."
@pytest.fixture
def browser_magic_case():
category = TicketCategory.objects.create(title="پشتیبانی لینک", slug="browser-magic")
owner = User.objects.create_user(username="browser-link-owner", mobile="09123000101", account_type="CUSTOMER")
other = User.objects.create_user(username="browser-link-other", mobile="09123000102", account_type="CUSTOMER", password=PASSWORD)
CustomerProfile.objects.create(user=owner)
CustomerProfile.objects.create(user=other)
ticket = create_ticket(customer=owner, category=category, subject="درخواست مرتبط با لینک", body="محتوای تیکت مرتبط")
token = TicketMagicLink.issue(ticket)
return other, ticket, token
@pytest.mark.parametrize("width", [320, 390, 1280])
def test_confirmed_logout_opens_ticket_with_readable_responsive_page(browser_page, live_server, browser_magic_case, width):
other, ticket, token = browser_magic_case
with browser_page() as page:
page.set_viewport_size({"width": width, "height": 844 if width < 640 else 900})
AuthenticationPage(page, live_server.url).login_customer(other, PASSWORD)
confirmation = MagicLinkConfirmationPage(page, live_server.url)
confirmation.open(token)
expect(page.locator("#confirmation-description")).to_have_text(MESSAGE)
expect(page.get_by_text("محتوای تیکت مرتبط", exact=True)).to_have_count(0)
confirmation.expect_readable_layout()
confirmation.continue_button.focus()
expect(confirmation.continue_button).to_be_focused()
assert confirmation.continue_button.evaluate("node => getComputedStyle(node).outlineStyle") == "solid"
artifact = Path("test-results/e2e") / f"magic-confirmation-{width}.png"
artifact.parent.mkdir(parents=True, exist_ok=True)
page.screenshot(path=str(artifact), full_page=True)
confirmation.continue_to_ticket(ticket)
TicketConversationPage(page, live_server.url).send_reply("پاسخ مرورگر پس از خروج")
confirmation.goto(reverse("accounts:profile"))
expect(page).to_have_url(confirmation.url(reverse("accounts:login")) + "?next=" + reverse("accounts:profile"))
def test_cancel_returns_to_logged_in_account(browser_page, live_server, browser_magic_case):
other, ticket, token = browser_magic_case
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_customer(other, PASSWORD)
confirmation = MagicLinkConfirmationPage(page, live_server.url)
confirmation.open(token)
confirmation.cancel()
confirmation.goto(reverse("accounts:profile"))
expect(page.get_by_role("heading", name="پروفایل من")).to_be_visible()
expect(page.locator("main")).to_contain_text(other.mobile)
def test_confirmation_form_works_without_its_javascript(browser_page, live_server, browser_magic_case):
other, ticket, token = browser_magic_case
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_customer(other, PASSWORD)
page.route("**/magic-link-confirmation.js*", lambda route: route.abort())
confirmation = MagicLinkConfirmationPage(page, live_server.url)
confirmation.open(token)
confirmation.continue_to_ticket(ticket)
+41
View File
@@ -0,0 +1,41 @@
from urllib.parse import urlsplit
import pytest
from playwright.sync_api import expect
from apps.core.models import SiteBranding
from apps.payments.access import issue_payment_link
from apps.payments import services
from tests.e2e.pages.payment_link import PaymentLinkPage
from tests.e2e.test_payments_e2e import payment_case, PNG
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
@pytest.mark.parametrize('width,linked', [(1440, True), (320, True), (390, False)])
def test_sms_payment_without_login_preserves_copy_and_submission(live_server, browser_page, payment_case, width, linked):
customer, account, reviewers = payment_case
notice = services.issue_notice(reviewers[0], {'customer': customer, 'account': account,
'subject': 'اعلامیه بدون ورود', 'amount': 12345, 'deposit_id': '000123', 'description': 'اطلاعات کامل اعلامیه'})
_, url = issue_payment_link(notice=notice)
branding = SiteBranding.load()
branding.developer_credit_text = 'امضای ثابت توسعه‌دهنده'
branding.save()
with browser_page() as page:
page.set_viewport_size({'width': width, 'height': 900})
view = PaymentLinkPage(page, live_server.url)
view.open_link(urlsplit(url).path)
expect(page.locator('.pay-deposit-required dd')).to_have_text('000123')
expect(page.get_by_text('واریز بدون این شناسه معتبر نخواهد بود.', exact=False)).to_be_visible()
expect(page.locator('.cs-footer')).to_have_text(branding.developer_credit_text)
expect(page.get_by_role('link', name='پروفایل من')).to_have_count(0)
assert page.locator('.pay-deposit-required dd').evaluate('(element) => getComputedStyle(element).color') == 'rgb(159, 31, 31)'
view.copy_all()
view.open_receipt_form(linked)
view.show_required_errors()
view.submit_payment(PNG, account_id=account.pk if not linked else None)
expect(page.get_by_text('در انتظار بررسی', exact=False).first).to_be_visible()
expect(page.get_by_role('button', name='ثبت تأیید من', exact=True)).to_have_count(0)
if not linked:
expect(page.get_by_text('واریز مستقل', exact=True)).to_be_visible()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
+361
View File
@@ -0,0 +1,361 @@
import base64
import pytest
from django.contrib.auth.models import Permission
from django.urls import reverse
from playwright.sync_api import expect
from apps.accounts.models import User
from apps.staff.models import StaffProfile
from apps.attachments.models import AttachmentPolicy
from apps.payments.models import BankInstitution, CompanyBankAccount, PaymentApprovalPolicy
from apps.payments import services
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.payments import PaymentsPage
pytestmark=[pytest.mark.e2e,pytest.mark.django_db(transaction=True)]
PASSWORD='PaymentE2e-safe-2026'
PNG=base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=')
@pytest.fixture
def payment_case(settings,tmp_path):
settings.MEDIA_ROOT=tmp_path
bank,_=BankInstitution.objects.get_or_create(code='melli',defaults={'name':'ملی'})
account=CompanyBankAccount.objects.create(name='حساب فروش شرکت',holder='شرکت',bank=bank,account_number='000123')
customer=User.objects.create_user(username='payment-e2e-customer',mobile='09120009021',password=PASSWORD,account_type='CUSTOMER')
reviewers=[]
for index in range(2):
user=User.objects.create_user(username=f'payment-e2e-reviewer-{index}',password=PASSWORD,account_type='STAFF')
StaffProfile.objects.create(user=user,display_name=f'تأییدکننده {index}')
user.user_permissions.add(Permission.objects.get(content_type__app_label='payments',codename='approve_paymentreceipt'))
reviewers.append(user)
policy=PaymentApprovalPolicy.objects.create(name='تأیید هر دو نفر')
policy.reviewers.add(*reviewers)
AttachmentPolicy.objects.update_or_create(attachment_type='IMAGE',defaults={'max_file_size':10*1024*1024,'allowed_extensions':['.png'],'allowed_mime_types':['image/png']})
return customer,account,reviewers
def test_customer_hub_no_history_and_small_mobile_services(live_server,browser_page,payment_case):
customer,account,reviewers=payment_case
with browser_page() as page:
page.set_viewport_size({'width':320,'height':568})
AuthenticationPage(page,live_server.url).login_customer(customer,PASSWORD)
PaymentsPage(page,live_server.url).open_hub()
expect(page.get_by_role('heading',name='درخواست‌های اخیر شما')).to_have_count(0)
for name in ['ارتباط با کارشناسان','ارسال فیش واریزی','شماره حساب‌ها']:
expect(page.get_by_role('heading',name=name,exact=True)).to_be_in_viewport()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
page.get_by_role('link',name='ثبت درخواست',exact=True).click()
expect(page).to_have_url(live_server.url+reverse('tickets:new'))
def test_independent_receipt_and_two_person_approval(live_server,browser_page,payment_case):
customer,account,reviewers=payment_case
with browser_page() as page:
AuthenticationPage(page,live_server.url).login_customer(customer,PASSWORD)
PaymentsPage(page,live_server.url).send_receipt(account.pk,PNG)
expect(page.get_by_text('واریز مستقل',exact=True)).to_be_visible()
# Read the public identifier from real navigation; no ORM inside Playwright's loop.
receipt_id=page.url.rstrip('/').rsplit('/',1)[-1]
page.goto(live_server.url+reverse('accounts:staff_login'))
# Explicit logout using the real page control preserves session/CSRF behavior.
page.get_by_role('button',name='خروج',exact=True).first.click()
AuthenticationPage(page,live_server.url).login_staff(reviewers[0],PASSWORD)
PaymentsPage(page,live_server.url).approve(receipt_id)
expect(page.get_by_text('تأیید ناقص',exact=True)).to_be_visible()
page.get_by_role('button',name='خروج',exact=True).first.click()
AuthenticationPage(page,live_server.url).login_staff(reviewers[1],PASSWORD)
PaymentsPage(page,live_server.url).approve(receipt_id)
expect(page.get_by_text('تأیید شده',exact=True)).to_be_visible()
expect(page.get_by_role('button',name='ثبت تأیید من')).to_have_count(0)
def test_form_errors_focus_and_full_copy(live_server,browser_page,payment_case):
customer,account,reviewers=payment_case
notice=services.issue_notice(reviewers[0],{'customer':customer,'account':account,'amount':12345,'subject':'فاکتور نمونه','description':'توضیح ثابت و کامل','deposit_id':'00099'})
with browser_page() as page:
AuthenticationPage(page,live_server.url).login_customer(customer,PASSWORD)
page.goto(live_server.url+reverse('payments:receipt_new'))
page.get_by_role('button',name='ثبت و ارسال فیش').click()
expect(page.locator('#receipt-errors')).to_be_visible()
expect(page.locator('#id_paid_amount')).to_be_focused()
page.goto(live_server.url+reverse('payments:notices')+'?notice='+str(notice.public_id))
expect(page.get_by_text('توضیح ثابت و کامل',exact=True)).to_be_visible()
page.get_by_role('button',name='کپی کامل اطلاعات').click()
# Browser may deny Clipboard on LAN HTTP; accessible manual fallback is valid.
expect(page.get_by_text('کپی شد',exact=True).or_(page.get_by_role('heading',name='متن را انتخاب و کپی کنید'))).to_be_visible()
def test_amount_grouping_and_jalali_leap_calendar(live_server,browser_page,payment_case):
customer,account,reviewers=payment_case
with browser_page() as page:
assets=[]
page.on('request',lambda r:assets.append(r.url) if r.resource_type in {'script','stylesheet','font','image'} else None)
AuthenticationPage(page,live_server.url).login_customer(customer,PASSWORD)
page.goto(live_server.url+reverse('payments:receipt_new'))
amount=page.locator('#id_paid_amount')
amount.fill('12345')
expect(amount).to_have_value('۱۲٬۳۴۵')
expect(page.locator('#amount-words')).to_contain_text('دهم تومان')
amount.fill('12.5')
expect(amount).to_have_value('12.5')
page.get_by_role('button',name='ثبت و ارسال فیش').click()
expect(amount).to_be_focused()
expect(page.locator('#error-paid_amount')).to_contain_text('عدد صحیح')
page.locator('#id_paid_year').select_option('1403')
page.locator('#id_paid_month').select_option('12')
page.locator('#id_paid_day').select_option('30')
expect(page.locator('#paid-date-iso')).to_have_value('2025-03-20')
page.locator('#id_paid_year').select_option('1402')
expect(page.locator('#id_paid_day')).to_have_value('')
expect(page.locator('#paid-date-iso')).to_have_value('')
assert assets and all(url.startswith(live_server.url+'/') or url.startswith('data:') for url in assets)
def test_staff_issues_identifier_notice_and_customer_sends_linked_receipt(live_server,browser_page,payment_case):
customer,account,reviewers=payment_case
account.requires_deposit_id=True;account.save()
with browser_page() as page:
AuthenticationPage(page,live_server.url).login_staff(reviewers[0],PASSWORD)
page.goto(live_server.url+reverse('staff_payments:desk'))
page.get_by_role('link',name='ایجاد اعلامیه واریز',exact=True).click()
PaymentsPage(page,live_server.url).select_customer(customer.local_mobile)
page.locator('#id_account').select_option(str(account.pk))
page.locator('#id_subject').fill('پرداخت فاکتور اختصاصی')
page.locator('#id_amount').fill('100000')
expect(page.locator('#id_amount')).to_have_value('۱۰۰٬۰۰۰')
expect(page.locator('#id_deposit_id')).to_have_attribute('required','')
page.locator('#id_deposit_id').fill('۰۰۱۲۳')
page.locator('#id_description').fill('شناسه فقط برای همین واریز')
page.get_by_role('button',name='صدور و اطلاع‌رسانی').click()
expect(page.locator('.flash')).to_contain_text('اعلامیه صادر شد')
notice_id=page.url.rstrip('/').rsplit('/',1)[-1]
page.get_by_role('button',name='خروج',exact=True).first.click()
AuthenticationPage(page,live_server.url).login_customer(customer,PASSWORD)
page.goto(live_server.url+reverse('payments:notices')+'?notice='+notice_id)
expect(page.get_by_text('شناسه فقط برای همین واریز',exact=True)).to_be_visible()
page.get_by_role('link',name='ارسال فیش این واریز',exact=True).click()
expect(page.get_by_label('واریز مرتبط (پیشنهادی)',exact=True)).to_be_checked()
expect(page.locator('#id_destination_account')).to_have_value(str(account.pk))
expect(page.locator('#id_deposit_id')).to_have_value('00123')
page.locator('#id_paid_amount').fill('100000')
page.get_by_role('button',name='امروز',exact=True).click()
page.locator('#id_receipt_image').set_input_files({'name':'receipt.png','mimeType':'image/png','buffer':PNG})
page.get_by_role('button',name='ثبت و ارسال فیش').click()
expect(page.locator('.flash')).to_contain_text('فیش ثبت شد')
expect(page.get_by_text('واریز مستقل',exact=True)).to_have_count(0)
page.goto(live_server.url+reverse('payments:receipt_list'))
expect(page.locator('article.pay-card').filter(has_text='پرداخت فاکتور اختصاصی')).to_contain_text('در انتظار بررسی')
def test_staff_payment_filters_and_bank_selection(live_server, browser_page, payment_case):
customer, account, reviewers = payment_case
reviewers[0].user_permissions.add(Permission.objects.get(
content_type__app_label='payments', codename='manage_company_bankaccounts'))
CompanyBankAccount.objects.create(name='حساب دوم شرکت', holder='شرکت', bank=account.bank,
account_number='000456', is_active=False)
services.issue_notice(reviewers[0], {'customer': customer, 'account': account,
'amount': 100000, 'subject': 'پرداخت نمونه برای بررسی میز مالی', 'description': '', 'deposit_id': ''})
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_staff(reviewers[0], PASSWORD)
page.goto(live_server.url + reverse('staff_payments:desk'))
page.get_by_role('link', name='اعلامیه‌های واریز', exact=True).click()
expect(page.get_by_role('link', name='پرداخت نمونه برای بررسی میز مالی')).to_be_visible()
page.get_by_role('link', name='نیازمند اقدام من', exact=True).click()
page.get_by_role('button', name='جست‌وجو', exact=True).click()
expect(page.get_by_role('link', name='نیازمند اقدام من', exact=True)).to_have_attribute('aria-current', 'page')
expect(page.get_by_text('در حال حاضر فیشی برای اقدام شما وجود ندارد.', exact=True)).to_be_visible()
page.goto(live_server.url + reverse('staff_payments:banks'))
page.set_viewport_size({'width': 375, 'height': 812})
submit = page.get_by_role('button', name='اعمال بر انتخاب‌شده‌ها', exact=True)
expect(submit).to_be_disabled()
page.get_by_role('checkbox', name='انتخاب همه حساب‌های این صفحه', exact=True).check()
expect(submit).to_be_enabled()
expect(page.get_by_text('۲ حساب انتخاب شده', exact=True)).to_be_visible()
page.get_by_role('checkbox', name='انتخاب حساب دوم شرکت', exact=True).uncheck()
expect(page.get_by_text('۱ حساب انتخاب شده', exact=True)).to_be_visible()
expect(page.get_by_role('link', name='ویرایش', exact=True)).to_have_count(2)
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
def test_staff_bank_forms_and_receipt_customer_steps(live_server, browser_page, payment_case):
customer, account, reviewers = payment_case
reviewers[0].user_permissions.add(Permission.objects.get(
content_type__app_label='payments', codename='manage_company_bankaccounts'))
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_staff(reviewers[0], PASSWORD)
page.set_viewport_size({'width': 375, 'height': 812})
page.goto(live_server.url + reverse('staff_payments:bank_new'))
page.get_by_label('نام نمایشی حساب', exact=False).fill('حساب طراحی فرم')
page.locator('#id_bank').select_option(str(account.bank_id))
page.locator('#id_holder').fill('شرکت نمونه')
page.locator('#id_account_number').fill('123456789')
page.get_by_role('checkbox', name='شناسه واریز الزامی است', exact=False).check()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
page.get_by_role('button', name='ذخیره حساب', exact=True).click()
page.get_by_role('link', name='حساب طراحی فرم', exact=True).click()
expect(page.get_by_role('checkbox', name='شناسه واریز الزامی است', exact=False)).to_be_checked()
page.get_by_role('checkbox', name='قابل انتخاب کارشناسان', exact=False).uncheck()
page.get_by_role('button', name='ذخیره حساب', exact=True).click()
expect(page.locator('.pay-bank-row').filter(has_text='حساب طراحی فرم').get_by_text('غیرفعال', exact=True)).to_be_visible()
page.goto(live_server.url + reverse('staff_payments:receipt_customer'))
PaymentsPage(page,live_server.url).select_customer(customer.local_mobile)
page.get_by_role('button', name='ادامه', exact=True).click()
expect(page.get_by_role('heading', name='ثبت فیش برای مشتری', exact=True)).to_be_visible()
expect(page.locator('#id_paid_amount')).to_be_visible()
page.get_by_role('button', name='ثبت و ارسال فیش', exact=True).click()
expect(page.locator('#receipt-errors')).to_be_visible()
expect(page.locator('#id_paid_amount')).to_be_focused()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
def test_customer_search_disambiguation_and_context_shortcuts(live_server, browser_page, payment_case):
from tests.e2e.pages.ticket_notices import TicketNoticesPage
from apps.customers.models import CustomerProfile
from apps.tickets.models import Ticket, TicketCategory
from django.utils import timezone
customer, account, reviewers = payment_case
CustomerProfile.objects.create(user=customer, first_name='محمد', last_name='رضایی', organization_name='شرکت اول')
other = User.objects.create_user(username='other-payment-customer', mobile='09120009022', account_type='CUSTOMER')
CustomerProfile.objects.create(user=other, first_name='محمد', last_name='رضایی', organization_name='شرکت دوم')
ticket = Ticket.objects.create(customer=customer,
category=TicketCategory.objects.create(title='پرونده مالی', slug='finance-picker-e2e'),
subject='سابقه خرید برای انتخاب دقیق', owner=reviewers[0], selected_expert=reviewers[0],
origin_visibility='SELECTED', last_activity_at=timezone.now())
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_staff(reviewers[0], PASSWORD)
page.goto(live_server.url + reverse('staff_payments:notice_new'))
page.get_by_label('جست‌وجو و انتخاب مشتری', exact=False).fill('محمد رضایی')
expect(page.locator('.pay-customer-result')).to_have_count(2)
result = page.locator('.pay-customer-result').filter(has_text=customer.local_mobile)
result.locator('summary').click()
expect(result.get_by_role('link', name=ticket.ticket_number + ' · ' + ticket.subject)).to_be_visible()
result.get_by_role('button', name='انتخاب این مشتری', exact=True).click()
expect(page.locator('[data-selected-customer]')).to_contain_text('شرکت اول')
expect(page.locator('[data-selected-customer]')).to_contain_text(customer.local_mobile)
page.locator('#id_account').select_option(str(account.pk))
page.locator('#id_subject').fill('انتخاب مشتری باید دوباره انجام شود')
page.locator('#id_amount').fill('100000')
page.get_by_label('جست‌وجو و انتخاب مشتری', exact=False).fill('0912')
page.get_by_role('button', name='صدور و اطلاع‌رسانی', exact=True).click()
expect(page.get_by_label('جست‌وجو و انتخاب مشتری', exact=False)).to_be_focused()
page.goto(live_server.url + reverse('staff_tickets:detail', args=[ticket.public_id]))
TicketNoticesPage(page, live_server.url).toggle_notices()
page.locator('#customer-info').get_by_role('link', name='ایجاد اعلامیه واریز', exact=True).click()
expect(page).to_have_url(live_server.url + reverse('staff_payments:notice_new') + '?customer=' + str(customer.public_id))
expect(page.locator('[data-selected-customer]')).to_contain_text(customer.local_mobile)
page.goto(live_server.url + reverse('staff_tickets:contacts'))
page.locator('.contact-card').filter(has_text=customer.local_mobile).get_by_role('link', name='ایجاد اعلامیه واریز', exact=True).click()
expect(page.locator('[data-selected-customer]')).to_contain_text(customer.local_mobile)
def test_customer_finance_preview_and_notice_context(live_server, browser_page, payment_case):
customer, account, reviewers = payment_case
notice = services.issue_notice(reviewers[0], {'customer': customer, 'account': account,
'amount': 120000, 'subject': 'واریز مرتبط نمونه', 'description': 'توضیحات همیشه نمایان',
'invoice_number': 'INV-120', 'deposit_id': '00055'})
with browser_page() as page:
page.set_viewport_size({'width': 320, 'height': 568})
AuthenticationPage(page, live_server.url).login_customer(customer, PASSWORD)
page.goto(live_server.url + reverse('payments:notices') + '?notice=' + str(notice.public_id))
expect(page.get_by_text('توضیحات همیشه نمایان', exact=True)).to_be_visible()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
page.get_by_role('link', name='ارسال فیش این واریز').click()
expect(page.locator('[data-selected-notice-title]')).to_have_text(notice.subject)
expect(page.locator('[data-selected-notice-summary]')).to_contain_text('INV-120')
expect(page.locator('[data-selected-notice-link]')).to_have_attribute('href', reverse('payments:notice_detail', args=[notice.public_id]))
page.locator('#id_receipt_image').set_input_files({'name': 'receipt.png', 'mimeType': 'image/png', 'buffer': PNG})
expect(page.get_by_alt_text('پیش‌نمایش تصویر فیش')).to_be_visible()
expect(page.locator('#receipt-file-status')).to_contain_text('receipt.png')
page.get_by_role('button', name='حذف انتخاب').click()
expect(page.get_by_alt_text('پیش‌نمایش تصویر فیش')).to_be_hidden()
page.locator('#id_paid_amount').fill('120000')
page.get_by_role('button', name='امروز', exact=True).click()
page.get_by_role('button', name='ثبت و ارسال فیش').click()
expect(page.locator('#error-receipt_image')).to_be_visible()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
def test_customer_linked_receipt_status_and_reason_without_access_roster(live_server, browser_page, payment_case):
customer, account, reviewers = payment_case
notice = services.issue_notice(reviewers[0], {'customer': customer, 'account': account,
'amount': 12345, 'subject': 'بررسی فیش مرتبط', 'description': 'جزئیات واریز'})
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_customer(customer, PASSWORD)
page.goto(live_server.url + reverse('payments:receipt_new') + '?notice=' + str(notice.public_id))
page.locator('#id_paid_amount').fill('12345')
page.get_by_role('button', name='امروز', exact=True).click()
page.locator('#id_receipt_image').set_input_files({'name': 'receipt.png', 'mimeType': 'image/png', 'buffer': PNG})
page.get_by_role('button', name='ثبت و ارسال فیش').click()
expect(page.locator('.flash')).to_contain_text('فیش ثبت شد')
receipt_id = page.url.rstrip('/').rsplit('/', 1)[-1]
page.get_by_role('button', name='خروج', exact=True).first.click()
AuthenticationPage(page, live_server.url).login_staff(reviewers[1], PASSWORD)
page.goto(live_server.url + reverse('staff_payments:receipt_detail', args=[receipt_id]))
expect(page.get_by_role('button', name='ثبت تأیید من', exact=True)).to_be_visible()
expect(page.get_by_role('heading', name='افراد تعیین‌شده', exact=True)).to_have_count(0)
page.locator('#decision-reason').fill('تصویر فیش خوانا نیست')
page.get_by_role('button', name='رد فیش', exact=True).click()
expect(page.get_by_text('تصویر فیش خوانا نیست', exact=True)).to_be_visible()
page.get_by_role('button', name='خروج', exact=True).first.click()
AuthenticationPage(page, live_server.url).login_customer(customer, PASSWORD)
page.goto(live_server.url + reverse('payments:notices') + '?notice=' + str(notice.public_id))
expect(page.get_by_text('تصویر فیش خوانا نیست', exact=True)).to_be_visible()
page.get_by_role('link', name='جزئیات فیش و سوابق بررسی', exact=True).click()
expect(page.get_by_text('تصویر فیش خوانا نیست', exact=True)).to_be_visible()
expect(page.get_by_role('heading', name='افراد تعیین‌شده', exact=True)).to_have_count(0)
expect(page.get_by_role('heading', name='سوابق ارسال و اصلاح', exact=True)).to_be_visible()
def test_notice_permalink_and_staff_receipt_preview(live_server, browser_page, payment_case):
customer, account, reviewers = payment_case
notice = services.issue_notice(reviewers[0], {'customer': customer, 'account': account,
'amount': 12000, 'subject': 'واریز با لینک مستقل', 'description': 'اطلاعات واریز'})
with browser_page() as page:
page.set_viewport_size({'width': 375, 'height': 812})
AuthenticationPage(page, live_server.url).login_customer(customer, PASSWORD)
page.goto(live_server.url + reverse('payments:notices'))
page.get_by_role('link', name='جزئیات واریز و فیش‌ها', exact=True).click()
expect(page).to_have_url(live_server.url + reverse('payments:notice_detail', args=[notice.public_id]))
expect(page.get_by_text('اطلاعات واریز', exact=True)).to_be_visible()
page.get_by_role('link', name='ارسال فیش این واریز', exact=True).click()
page.locator('[data-selected-notice-link]').click()
expect(page).to_have_url(live_server.url + reverse('payments:notice_detail', args=[notice.public_id]))
page.get_by_role('button', name='خروج', exact=True).first.click()
AuthenticationPage(page, live_server.url).login_staff(reviewers[0], PASSWORD)
page.goto(live_server.url + reverse('staff_payments:receipt_new', args=[customer.public_id]) + '?notice=' + str(notice.public_id))
expect(page.get_by_role('heading', name='ثبت فیش برای مشتری', exact=True)).to_be_visible()
expect(page.locator('[data-selected-notice-link]')).to_have_attribute('href', reverse('staff_payments:notice_detail', args=[notice.public_id]))
page.locator('#id_paid_amount').fill('12000')
page.get_by_role('button', name='امروز', exact=True).click()
page.locator('#id_receipt_image').set_input_files({'name': 'receipt.png', 'mimeType': 'image/png', 'buffer': PNG})
expect(page.get_by_alt_text('پیش‌نمایش تصویر فیش')).to_be_visible()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
page.get_by_role('button', name='ثبت و ارسال فیش', exact=True).click()
expect(page.locator('.flash')).to_contain_text('فیش ثبت شد')
def test_notice_identifier_generation_preserves_manual_value_and_customer_context(live_server, browser_page, payment_case):
customer, account, reviewers = payment_case
account.requires_deposit_id = True
account.save()
with browser_page() as page:
page.set_viewport_size({'width': 375, 'height': 812})
AuthenticationPage(page, live_server.url).login_staff(reviewers[0], PASSWORD)
page.goto(live_server.url + reverse('staff_payments:notice_new'))
generate = page.get_by_role('button', name='تولید خودکار شناسه', exact=True)
expect(generate).to_be_disabled()
PaymentsPage(page, live_server.url).select_customer(customer.local_mobile)
page.locator('#id_account').select_option(str(account.pk))
expect(page.locator('#id_deposit_id')).to_have_attribute('required', '')
generate.click()
expect(page.locator('#id_deposit_id')).to_have_value('0912000902169')
page.locator('#id_deposit_id').fill('manual-123')
generate.click()
expect(page.locator('#identifier-candidate')).to_have_text('0912000902169')
expect(page.locator('#id_deposit_id')).to_have_value('manual-123')
page.get_by_role('button', name='جایگزینی شناسه', exact=True).click()
expect(page.locator('#id_deposit_id')).to_have_value('0912000902169')
page.get_by_role('button', name='تغییر مشتری', exact=True).click()
expect(page.locator('#id_deposit_id')).to_have_value('')
expect(generate).to_be_disabled()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
+53
View File
@@ -0,0 +1,53 @@
import pytest
from django.urls import reverse
from django.utils import timezone
from playwright.sync_api import expect
from apps.accounts.models import User
from apps.payments import services
from apps.payments.models import BankInstitution, CompanyBankAccount
from apps.staff.models import StaffProfile
from apps.tickets.models import Ticket, TicketCategory
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.ticket_notices import TicketNoticesPage
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
@pytest.mark.parametrize('width', [1440, 390])
def test_ticket_notices_toggle_and_customer_navigation(live_server, browser_page, width):
password = 'TicketNotices-e2e-2026'
staff = User.objects.create_user(username='notice-staff', password=password, account_type='STAFF')
StaffProfile.objects.create(user=staff, display_name='کارشناس اعلامیه')
customer = User.objects.create_user(username='notice-customer', account_type='CUSTOMER')
bank, _ = BankInstitution.objects.get_or_create(code='melli', defaults={'name': 'ملی'})
account = CompanyBankAccount.objects.create(name='حساب اعلامیه', holder='شرکت', bank=bank, account_number='123')
notices = [services.issue_notice(staff, {'customer': customer, 'account': account,
'subject': f'اعلامیه نمونه {index}', 'amount': 12345}) for index in range(4)]
ticket = Ticket.objects.create(customer=customer,
category=TicketCategory.objects.create(title='مالی', slug='ticket-notices'),
subject='تیکت اعلامیه', owner=staff, selected_expert=staff,
origin_visibility='SELECTED', last_activity_at=timezone.now())
ticket_url = reverse('staff_tickets:detail', args=[ticket.public_id])
with browser_page() as page:
page.set_viewport_size({'width': width, 'height': 900})
AuthenticationPage(page, live_server.url).login_staff(staff, password)
view = TicketNoticesPage(page, live_server.url)
view.goto(ticket_url)
view.open_customer_panel()
view.expect_closed()
view.toggle_notices()
view.expect_open()
expect(page.get_by_role('list', name='اعلامیه‌های اخیر مشتری')).to_contain_text('در انتظار واریز')
expect(page.get_by_role('list', name='اعلامیه‌های اخیر مشتری')).not_to_contain_text(notices[0].subject)
view.toggle_notices()
view.expect_closed()
view.toggle_notices()
view.open_notice(notices[-1].subject)
expect(page).to_have_url(live_server.url + reverse('staff_payments:notice_detail', args=[notices[-1].public_id]))
view.goto(ticket_url)
view.open_customer_panel()
view.toggle_notices()
view.open_all()
expect(page).to_have_url(live_server.url + reverse('staff_payments:desk') + f'?tab=notices&customer={customer.pk}')
expect(page.get_by_text(notices[0].subject, exact=True)).to_be_visible()