Add refusals tab to the order dashboard
Orders whose Nova Poshta status is a refusal (102/103/105/108) now show only under «Отказы», whether or not they have a receipt. GET /orders takes tab=no_receipt|has_receipt|refused instead of has_receipt. NP statuses are also polled for orders that already have a receipt, until NP reports a final status. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,8 +69,8 @@ def _patch_orders_service(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def fake_sync(session: object, crm: object) -> None:
|
||||
return None
|
||||
|
||||
async def fake_list(session: object, *, has_receipt: bool) -> list[Order]:
|
||||
return [] if has_receipt else [_order()]
|
||||
async def fake_list(session: object, *, tab: str) -> list[Order]:
|
||||
return [_order()] if tab == "no_receipt" else []
|
||||
|
||||
async def fake_delete(session: object, order_id: str) -> Order | None:
|
||||
return _order(order_id) if order_id == "1" else None
|
||||
@@ -132,7 +132,7 @@ class TestListOrders:
|
||||
) -> None:
|
||||
app.dependency_overrides[get_current_user] = lambda: _user(role)
|
||||
|
||||
response = client.get("/api/v1/orders", params={"has_receipt": "false"})
|
||||
response = client.get("/api/v1/orders", params={"tab": "no_receipt"})
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
@@ -142,14 +142,22 @@ class TestListOrders:
|
||||
assert body[0]["has_receipt"] is False
|
||||
assert body[0]["goods"][0]["sku"] == "SKU-1"
|
||||
|
||||
def test_has_receipt_tab_is_empty_for_now(self, client: TestClient) -> None:
|
||||
@pytest.mark.parametrize("tab", ["has_receipt", "refused"])
|
||||
def test_other_tabs_are_passed_to_service(self, client: TestClient, tab: str) -> None:
|
||||
app.dependency_overrides[get_current_user] = lambda: _user(UserRole.CASHIER)
|
||||
|
||||
response = client.get("/api/v1/orders", params={"has_receipt": "true"})
|
||||
response = client.get("/api/v1/orders", params={"tab": tab})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == []
|
||||
|
||||
def test_unknown_tab_is_rejected(self, client: TestClient) -> None:
|
||||
app.dependency_overrides[get_current_user] = lambda: _user(UserRole.CASHIER)
|
||||
|
||||
response = client.get("/api/v1/orders", params={"tab": "bogus"})
|
||||
|
||||
assert response.status_code == 422
|
||||
|
||||
def test_requires_authentication(self, client: TestClient) -> None:
|
||||
response = client.get("/api/v1/orders")
|
||||
assert response.status_code == 401
|
||||
|
||||
@@ -7,10 +7,18 @@ from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
from app.db.models.order import Order
|
||||
from app.schemas.orders import OrderUpdateIn
|
||||
from app.services.orders import OrderEditError, _parse_crm_datetime, _to_kopecks, update_order
|
||||
from app.services.orders import (
|
||||
OrderEditError,
|
||||
OrderTab,
|
||||
_parse_crm_datetime,
|
||||
_to_kopecks,
|
||||
list_orders,
|
||||
update_order,
|
||||
)
|
||||
|
||||
|
||||
class TestToKopecks:
|
||||
@@ -148,3 +156,37 @@ class TestUpdateOrder:
|
||||
assert asyncio.run(update_order(_FakeSession(None), "1", payload)) is None # type: ignore[arg-type]
|
||||
deleted = _FakeSession(_order(is_deleted=True))
|
||||
assert asyncio.run(update_order(deleted, "1", payload)) is None # type: ignore[arg-type]
|
||||
|
||||
|
||||
class _CapturingSession:
|
||||
def __init__(self) -> None:
|
||||
self.statement: Any = None
|
||||
|
||||
async def scalars(self, statement: Any) -> list[Order]:
|
||||
self.statement = statement
|
||||
return []
|
||||
|
||||
|
||||
def _list_where(tab: OrderTab) -> str:
|
||||
"""WHERE-часть запроса вкладки (SELECT перечисляет все колонки, в т.ч. receipt_created_at)."""
|
||||
session = _CapturingSession()
|
||||
asyncio.run(list_orders(session, tab=tab)) # type: ignore[arg-type]
|
||||
sql = str(
|
||||
session.statement.compile(
|
||||
dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True}
|
||||
)
|
||||
)
|
||||
return sql.split("WHERE", 1)[1]
|
||||
|
||||
|
||||
class TestListOrders:
|
||||
def test_refused_tab_filters_by_refusal_codes_only(self) -> None:
|
||||
sql = _list_where(OrderTab.REFUSED)
|
||||
assert "orders.np_status_code IN ('102', '103', '105', '108')" in sql
|
||||
assert "receipt_created_at" not in sql
|
||||
|
||||
@pytest.mark.parametrize("tab", [OrderTab.NO_RECEIPT, OrderTab.HAS_RECEIPT])
|
||||
def test_other_tabs_exclude_refusals(self, tab: OrderTab) -> None:
|
||||
sql = _list_where(tab)
|
||||
assert "NOT IN ('102', '103', '105', '108')" in sql
|
||||
assert "receipt_created_at" in sql
|
||||
|
||||
Reference in New Issue
Block a user