improve bazdid 4
This commit is contained in:
1 parent
8fd881e0ea
commit
825f2a5973
13 files changed
+585
-15
No files matched your search
@@ -2106,3 +2106,263 @@ class TodayFollowupTests(TestCase):
|
||||
self._coordination(customer, -4)
|
||||
html = self.client.get(self.URL + '?view=uncoordinated').content.decode()
|
||||
self.assertNotIn('class="ez-overdue-alert"', html)
|
||||
|
||||
|
||||
class ActionSoftDeleteTests(TestCase):
|
||||
"""حذف (soft-delete) یک اقدام: status='cancell' + حسابرسی.
|
||||
|
||||
قواعد قفلشده:
|
||||
* حذف ⇒ اقدام از `entezar`، `ActionList` و داشبورد خارج میشود.
|
||||
* رکورد فیزیکی حذف **نمیشود** (سابقه برای حسابرسی میماند).
|
||||
* اقدام دارای نظرسنجی قابل حذف نیست.
|
||||
* پیامک ارسالنشدهٔ مرتبط لغو میشود.
|
||||
* `last_bazdid_dt` بازمحاسبه میشود.
|
||||
* GET بیاثر است؛ URL فقط POST میپذیرد.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = make_user('deleter', superuser=True)
|
||||
self.client.force_login(self.user)
|
||||
self.customer = make_customer('مشتری حذف')
|
||||
Customer.objects.filter(pk=self.customer.pk).update(
|
||||
installDate=timezone.localdate() - timedelta(days=500))
|
||||
self.customer.refresh_from_db()
|
||||
|
||||
def _action(self, **kwargs):
|
||||
defaults = dict(customer=self.customer, actionType='hamahangi',
|
||||
status='finish', actionDateTime=timezone.now(),
|
||||
finishDateTime=timezone.now())
|
||||
defaults.update(kwargs)
|
||||
return Action.objects.create(**defaults)
|
||||
|
||||
def _delete(self, action):
|
||||
return self.client.post(
|
||||
reverse('admin_panel:customerActionDelete',
|
||||
args=[self.customer.pk, action.pk]))
|
||||
|
||||
def _followup_ids(self):
|
||||
response = self.client.get('/panel/entezar/?view=today')
|
||||
return {o.id for o in response.context['customers']}
|
||||
|
||||
# ---------- رفتار اصلی ----------
|
||||
def test_delete_marks_cancelled_with_audit(self):
|
||||
action = self._action()
|
||||
self._delete(action)
|
||||
action.refresh_from_db()
|
||||
self.assertEqual(action.status, 'cancell')
|
||||
self.assertIsNotNone(action.deleted_at)
|
||||
self.assertEqual(action.deleted_by_id, self.user.pk)
|
||||
self.assertTrue(action.is_deleted)
|
||||
|
||||
def test_record_is_not_physically_deleted(self):
|
||||
action = self._action()
|
||||
pk = action.pk
|
||||
self._delete(action)
|
||||
self.assertTrue(Action.objects.filter(pk=pk).exists(),
|
||||
'رکورد باید برای حسابرسی باقی بماند')
|
||||
|
||||
def test_deleted_action_leaves_followup_list(self):
|
||||
action = self._action()
|
||||
self.assertIn(self.customer.id, self._followup_ids())
|
||||
self._delete(action)
|
||||
self.assertNotIn(self.customer.id, self._followup_ids())
|
||||
|
||||
def test_get_request_does_not_delete(self):
|
||||
action = self._action()
|
||||
response = self.client.get(reverse(
|
||||
'admin_panel:customerActionDelete',
|
||||
args=[self.customer.pk, action.pk]))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
action.refresh_from_db()
|
||||
self.assertIsNone(action.deleted_at)
|
||||
|
||||
def test_cannot_delete_twice(self):
|
||||
action = self._action()
|
||||
self._delete(action)
|
||||
# بار دوم باید خطا بدهد ولی کرش نکند
|
||||
response = self._delete(action)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
action.refresh_from_db()
|
||||
self.assertEqual(action.status, 'cancell')
|
||||
|
||||
# ---------- ممنوعیت نظرسنجی ----------
|
||||
def test_action_with_survey_cannot_be_deleted(self):
|
||||
action = self._action(nazarDT=timezone.now(), nazar='خوب', grade=20)
|
||||
self._delete(action)
|
||||
action.refresh_from_db()
|
||||
self.assertEqual(action.status, 'finish', 'نباید وضعیت عوض شود')
|
||||
self.assertIsNone(action.deleted_at)
|
||||
|
||||
def test_delete_blocked_message_shown(self):
|
||||
action = self._action(nazarDT=timezone.now(), nazar='خوب', grade=20)
|
||||
response = self._delete(action)
|
||||
messages_list = list(response.wsgi_request._messages)
|
||||
self.assertTrue(any('نظرسنجی' in str(m) for m in messages_list))
|
||||
|
||||
# ---------- پیامک ----------
|
||||
def test_pending_visit_sms_is_cancelled(self):
|
||||
from admin_panel.models import SMSQueue, SMSTemplate
|
||||
SMSTemplate.objects.update_or_create(
|
||||
sms_type='visit_created',
|
||||
defaults={'text': 'بازدید {name}', 'is_enabled': True})
|
||||
PhoneNumber.objects.create(customer=self.customer, title='موبایل',
|
||||
phone_number='09120000000')
|
||||
Customer.objects.filter(pk=self.customer.pk).update(phone1='09120000000')
|
||||
self.customer.refresh_from_db()
|
||||
|
||||
from admin_panel.sms.triggers import notify_visit_scheduled
|
||||
action = self._action(actionType='bazdid', status='waite')
|
||||
notify_visit_scheduled(self.customer, action, created_by=self.user)
|
||||
self.assertEqual(
|
||||
SMSQueue.objects.filter(customer=self.customer,
|
||||
sms_type='visit_created').count(), 1)
|
||||
|
||||
self._delete(action)
|
||||
self.assertEqual(
|
||||
SMSQueue.objects.filter(customer=self.customer,
|
||||
sms_type='visit_created',
|
||||
status=SMSQueue.Status.CANCELLED).count(), 1)
|
||||
|
||||
# ---------- بازمحاسبهٔ ستون ----------
|
||||
def test_last_bazdid_recalculated_after_delete(self):
|
||||
old_visit = self._action(actionType='bazdid',
|
||||
actionDateTime=at(-400), finishDateTime=at(-400))
|
||||
recent = self._action(actionType='bazdid',
|
||||
actionDateTime=at(-2), finishDateTime=at(-2))
|
||||
refresh_last_bazdid(self.customer, reason='create', user=self.user)
|
||||
self.customer.refresh_from_db()
|
||||
self.assertEqual(self.customer.last_bazdid_dt.date(), at(-2).date())
|
||||
|
||||
self._delete(recent)
|
||||
self.customer.refresh_from_db()
|
||||
self.assertEqual(self.customer.last_bazdid_dt.date(), at(-400).date())
|
||||
self.assertTrue(Action.objects.filter(pk=old_visit.pk).exists())
|
||||
|
||||
# ---------- سایر نماها ----------
|
||||
def test_deleted_action_hidden_from_customer_action_list(self):
|
||||
action = self._action()
|
||||
self._delete(action)
|
||||
html = self.client.get(
|
||||
'/panel/customer/%d/action' % self.customer.pk).content.decode()
|
||||
self.assertNotIn('action_id=\'%d\'' % action.pk, html)
|
||||
|
||||
def test_deleted_action_visible_with_explicit_filter(self):
|
||||
"""با فیلتر صریح status=cancell باید دیده شود (حسابرسی)."""
|
||||
action = self._action()
|
||||
self._delete(action)
|
||||
html = self.client.get(
|
||||
'/panel/customer/%d/action?status=cancell' % self.customer.pk
|
||||
).content.decode()
|
||||
self.assertIn('action_id=\'%d\'' % action.pk, html)
|
||||
|
||||
def test_deleted_action_hidden_from_actionlist_all(self):
|
||||
action = self._action(status='waite', forUser=self.user)
|
||||
self._delete(action)
|
||||
response = self.client.get(reverse('admin_panel:ActionListAll'))
|
||||
ids = {a.pk for a in response.context['actions']}
|
||||
self.assertNotIn(action.pk, ids)
|
||||
|
||||
def test_deleted_action_hidden_from_nazar_page(self):
|
||||
action = self._action(nazarDT=None)
|
||||
self._delete(action)
|
||||
response = self.client.get(
|
||||
'/panel/customer/%d/nazar/' % self.customer.pk)
|
||||
ids = {a.pk for a in response.context['actions']}
|
||||
self.assertNotIn(action.pk, ids)
|
||||
|
||||
|
||||
class ActionDeleteUITests(TestCase):
|
||||
"""دکمهٔ حذف روی صفحهٔ اقدامها: حضور، قفل نظرسنجی و اسکوپ."""
|
||||
|
||||
def setUp(self):
|
||||
self.user = make_user('deleteUi', superuser=True)
|
||||
self.client.force_login(self.user)
|
||||
self.customer = make_customer('UI حذف')
|
||||
Customer.objects.filter(pk=self.customer.pk).update(
|
||||
installDate=timezone.localdate() - timedelta(days=500))
|
||||
self.customer.refresh_from_db()
|
||||
|
||||
def _action(self, **kwargs):
|
||||
defaults = dict(customer=self.customer, actionType='hamahangi',
|
||||
status='waite', actionDateTime=timezone.now())
|
||||
defaults.update(kwargs)
|
||||
return Action.objects.create(**defaults)
|
||||
|
||||
def test_delete_button_rendered_on_customer_action_page(self):
|
||||
action = self._action()
|
||||
html = self.client.get(
|
||||
'/panel/customer/%d/action' % self.customer.pk).content.decode()
|
||||
self.assertIn('/action/%d/delete' % action.pk, html)
|
||||
self.assertIn('ax-del is-danger', html)
|
||||
self.assertIn('method="post"', html)
|
||||
self.assertIn('csrfmiddlewaretoken', html)
|
||||
# تأیید پیش از حذف
|
||||
self.assertIn('confirm(', html)
|
||||
|
||||
def test_delete_button_absent_on_actionlist(self):
|
||||
action = self._action(forUser=self.user)
|
||||
html = self.client.get(
|
||||
'/panel/ActionList/%d/today' % self.user.pk).content.decode()
|
||||
self.assertNotIn('ax-del is-danger', html,
|
||||
'دکمهٔ حذف نباید در فهرست وظایف ظاهر شود')
|
||||
self.assertNotIn('/action/%d/delete' % action.pk, html)
|
||||
|
||||
def test_survey_action_shows_locked_state(self):
|
||||
self._action(nazarDT=timezone.now(), nazar='خوب', grade=20)
|
||||
html = self.client.get(
|
||||
'/panel/customer/%d/action' % self.customer.pk).content.decode()
|
||||
self.assertIn('ax-del is-locked', html)
|
||||
self.assertNotIn('ax-del is-danger', html)
|
||||
|
||||
def test_deleted_action_hidden_by_default(self):
|
||||
kept = self._action()
|
||||
gone = self._action()
|
||||
self.client.post(reverse('admin_panel:customerActionDelete',
|
||||
args=[self.customer.pk, gone.pk]))
|
||||
html = self.client.get(
|
||||
'/panel/customer/%d/action' % self.customer.pk).content.decode()
|
||||
self.assertIn("/action/%d/delete" % kept.pk, html)
|
||||
self.assertNotIn("/action/%d/delete" % gone.pk, html)
|
||||
|
||||
|
||||
class DeleteButtonDoesNotOpenModalTests(TestCase):
|
||||
"""کلیک روی «حذف» نباید مودال ویرایش را باز کند.
|
||||
|
||||
دکمهٔ حذف داخل کارتی است که کلاس ``actionBoxOpenB`` دارد و هندلرِ بازکردنِ
|
||||
مودال روی همان کارت بسته شده است؛ بدون محافظ، رویداد به والد میرسد و
|
||||
مودالِ ویرایش هم باز میشد. دو لایه محافظ گذاشته شده است:
|
||||
|
||||
۱) گاردِ ``closest('.ax-del-form, .ax-del')`` در هندلرِ ``actionBoxOpenB``
|
||||
۲) ``stopPropagation`` روی خودِ فرم/دکمهٔ حذف
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = make_user('noModalOnDelete', superuser=True)
|
||||
self.client.force_login(self.user)
|
||||
self.customer = make_customer('حذف بدون مودال')
|
||||
self.action = make_visit(self.customer, action_dt=at(-1),
|
||||
finish_dt=at(-1), action_type='hamahangi')
|
||||
self.html = self.client.get(
|
||||
'/panel/customer/%d/action' % self.customer.pk).content.decode()
|
||||
|
||||
def test_modal_handler_guards_delete_controls(self):
|
||||
self.assertIn("closest('.ax-del-form, .ax-del')", self.html)
|
||||
|
||||
def test_delete_controls_stop_propagation(self):
|
||||
self.assertIn('stopPropagation', self.html)
|
||||
self.assertRegex(
|
||||
self.html,
|
||||
r"\$\('\.ax-del-form, \.ax-del'\)"
|
||||
r"\s*\n\s*\.off\('\.axdel'\)"
|
||||
r"\s*\n\s*\.on\('click\.axdel mousedown\.axdel'",
|
||||
)
|
||||
|
||||
def test_guard_precedes_modal_open_logic(self):
|
||||
"""گارد باید پیش از ``isEditMode = true`` اجرا شود."""
|
||||
guard = self.html.index("closest('.ax-del-form, .ax-del')")
|
||||
opener = self.html.index('isEditMode = true;', guard - 900)
|
||||
self.assertLess(guard, opener)
|
||||
|
||||
def test_delete_form_has_csrf_and_post(self):
|
||||
self.assertIn('ax-del-form', self.html)
|
||||
self.assertIn('csrfmiddlewaretoken', self.html)
|
||||
Reference in new issue
Block a user