feat: add party-size queueing and call modes
This commit is contained in:
@@ -153,13 +153,36 @@ def main() -> None:
|
||||
"smoke requires a freshly rebuilt, dedicated database with no waiting "
|
||||
"tickets; rebuild and seed the smoke database before running it"
|
||||
)
|
||||
project_config = snapshot["project"]
|
||||
assert project_config["call_mode"] == "BOTH", "smoke project must expose both call modes"
|
||||
party_size = min(
|
||||
int(project_config["max_party_size"]),
|
||||
max(int(project_config["min_party_size"]), 3),
|
||||
)
|
||||
phone = "139" + str(int(time.time() * 1000))[-8:]
|
||||
last_name = "烟测"
|
||||
honorific = "游客"
|
||||
missing_party = client.request(
|
||||
"POST",
|
||||
f"/api/staff/projects/{project_id}/tickets",
|
||||
{"phone": phone},
|
||||
key=str(uuid.uuid4()),
|
||||
expected=422,
|
||||
)
|
||||
assert error_code(missing_party) == "INVALID_PARTY_SIZE"
|
||||
oversized_party = client.request(
|
||||
"POST",
|
||||
f"/api/staff/projects/{project_id}/tickets",
|
||||
{"phone": phone, "party_size": int(project_config["max_party_size"]) + 1},
|
||||
key=str(uuid.uuid4()),
|
||||
expected=422,
|
||||
)
|
||||
assert error_code(oversized_party) == "INVALID_PARTY_SIZE"
|
||||
ticket_body = {
|
||||
"phone": phone,
|
||||
"last_name": last_name,
|
||||
"honorific": honorific,
|
||||
"party_size": party_size,
|
||||
"allow_duplicate": False,
|
||||
}
|
||||
first_key = str(uuid.uuid4())
|
||||
@@ -173,6 +196,7 @@ def main() -> None:
|
||||
first_ticket = first["ticket"]
|
||||
assert first_ticket["ticket_number"]
|
||||
assert first_ticket["public_token"]
|
||||
assert first_ticket["party_size"] == party_size
|
||||
assert_personal_continuity(
|
||||
first_ticket,
|
||||
phone=phone,
|
||||
@@ -217,6 +241,9 @@ def main() -> None:
|
||||
"GET", f"/api/public/status/{first_ticket['public_token']}"
|
||||
)
|
||||
assert public["ticket_number"] == first_ticket["ticket_number"]
|
||||
assert public["party_size"] == party_size
|
||||
assert public["people_ahead"] == 0
|
||||
assert public["estimated_wait"]["estimate_minutes"] == 0
|
||||
assert_public_phone_projection(public, phone, "public status")
|
||||
|
||||
phone_lookup = client.request(
|
||||
@@ -308,7 +335,19 @@ def main() -> None:
|
||||
assert waiting_ids == {ticket["id"] for ticket in created_tickets}, (
|
||||
"dedicated smoke queue contains unexpected tickets"
|
||||
)
|
||||
expected_waiting_people = sum(ticket["party_size"] for ticket in created_tickets)
|
||||
assert snapshot["metrics"]["waiting_ticket_count"] == len(created_tickets)
|
||||
assert snapshot["metrics"]["waiting_people_count"] == expected_waiting_people
|
||||
display_before_call = client.request(
|
||||
"GET", f"/api/display/{args.display_token}/snapshot"
|
||||
)
|
||||
assert display_before_call["waiting_ticket_count"] == len(created_tickets)
|
||||
assert display_before_call["waiting_people_count"] == expected_waiting_people
|
||||
assert display_before_call["experienced_people"] == max(
|
||||
int(project.get("experienced_people_start") or 0), expected_waiting_people
|
||||
)
|
||||
for ticket in waiting_before_call:
|
||||
assert ticket["party_size"] == party_size
|
||||
assert_personal_continuity(
|
||||
ticket,
|
||||
phone=phone,
|
||||
@@ -319,7 +358,7 @@ def main() -> None:
|
||||
stale_call = client.request(
|
||||
"POST",
|
||||
f"/api/staff/projects/{project_id}/call-next",
|
||||
{"expected_revision": max(0, int(snapshot["revision"]) - 1), "count": batch_size},
|
||||
{"expected_revision": max(0, int(snapshot["revision"]) - 1), "mode": "PEOPLE", "count": batch_size * party_size + party_size - 1},
|
||||
key=str(uuid.uuid4()),
|
||||
expected=409,
|
||||
)
|
||||
@@ -329,7 +368,8 @@ def main() -> None:
|
||||
ticket["id"] for ticket in waiting_before_call[:batch_size]
|
||||
]
|
||||
call_key = str(uuid.uuid4())
|
||||
call_body = {"expected_revision": int(snapshot["revision"]), "count": batch_size}
|
||||
people_target = batch_size * party_size + party_size - 1
|
||||
call_body = {"expected_revision": int(snapshot["revision"]), "mode": "PEOPLE", "count": people_target}
|
||||
called = client.request(
|
||||
"POST",
|
||||
f"/api/staff/projects/{project_id}/call-next",
|
||||
@@ -340,6 +380,12 @@ def main() -> None:
|
||||
assert [ticket["id"] for ticket in called_tickets] == expected_called_ids
|
||||
assert len(called_tickets) == min(batch_size, len(waiting_before_call))
|
||||
assert called_tickets, "call-next must call at least one waiting ticket"
|
||||
assert called["batch"]["call_mode"] == "PEOPLE"
|
||||
assert called["batch"]["requested_count"] == people_target
|
||||
assert called["batch"]["ticket_count"] == batch_size
|
||||
assert called["batch"]["people_count"] == batch_size * party_size
|
||||
assert called["batch"]["people_count"] <= people_target
|
||||
assert called["batch"]["people_count"] + waiting_before_call[batch_size]["party_size"] > people_target
|
||||
for ticket in called_tickets:
|
||||
assert_personal_continuity(
|
||||
ticket,
|
||||
@@ -375,6 +421,8 @@ def main() -> None:
|
||||
assert [ticket["id"] for ticket in current_batch["tickets"]] == (
|
||||
expected_called_ids
|
||||
)
|
||||
assert current_batch["call_mode"] == "PEOPLE"
|
||||
assert current_batch["people_count"] == batch_size * party_size
|
||||
for ticket in current_batch["tickets"]:
|
||||
assert_personal_continuity(
|
||||
ticket,
|
||||
@@ -387,16 +435,21 @@ def main() -> None:
|
||||
second_called = client.request(
|
||||
"POST",
|
||||
f"/api/staff/projects/{project_id}/call-next",
|
||||
{"expected_revision": int(called["revision"]), "count": 1},
|
||||
{"expected_revision": int(called["revision"]), "mode": "TICKET", "count": 1},
|
||||
key=str(uuid.uuid4()),
|
||||
)
|
||||
assert second_called["batch"]["id"] != called["batch"]["id"]
|
||||
assert len(second_called["batch"]["tickets"]) == 1
|
||||
assert second_called["batch"]["call_mode"] == "TICKET"
|
||||
assert second_called["batch"]["ticket_count"] == 1
|
||||
assert second_called["batch"]["people_count"] == party_size
|
||||
final_staff_snapshot = client.request(
|
||||
"GET", f"/api/staff/projects/{project_id}/queue"
|
||||
)
|
||||
assert final_staff_snapshot["current_batch"]["id"] == second_called["batch"]["id"]
|
||||
assert final_staff_snapshot["metrics"]["waiting_count"] == 0
|
||||
assert final_staff_snapshot["metrics"]["waiting_ticket_count"] == 0
|
||||
assert final_staff_snapshot["metrics"]["waiting_people_count"] == 0
|
||||
completed_public = client.request(
|
||||
"GET", f"/api/public/status/{first_ticket['public_token']}"
|
||||
)
|
||||
@@ -405,6 +458,9 @@ def main() -> None:
|
||||
display = client.request(
|
||||
"GET", f"/api/display/{args.display_token}/snapshot"
|
||||
)
|
||||
assert display["experienced_people"] == max(
|
||||
int(project_config.get("experienced_people_start") or 0), expected_waiting_people
|
||||
)
|
||||
serialized_display = json.dumps(display, ensure_ascii=False)
|
||||
display_keys = mapping_keys(display)
|
||||
assert "honorific" not in display_keys
|
||||
@@ -426,6 +482,11 @@ def main() -> None:
|
||||
overview = client.request("GET", "/api/admin/overview")
|
||||
project = next(item for item in overview["projects"] if item["id"] == project_id)
|
||||
assert project["waiting_count"] == 0
|
||||
assert project["waiting_ticket_count"] == 0
|
||||
assert project["waiting_people_count"] == 0
|
||||
assert project["experienced_people"] == max(
|
||||
int(project_config.get("experienced_people_start") or 0), expected_waiting_people
|
||||
)
|
||||
current_admin_ticket = ticket_with_number(
|
||||
overview.get("active_tickets"),
|
||||
second_called["batch"]["tickets"][0]["ticket_number"],
|
||||
@@ -440,6 +501,36 @@ def main() -> None:
|
||||
source="admin active_tickets after automatic rotation",
|
||||
)
|
||||
|
||||
public_projects = client.request("GET", "/api/public/projects")
|
||||
assert any(item.get("id") == project_id for item in public_projects.get("projects", []))
|
||||
public_phone = "137" + str(int(time.time() * 1000))[-8:]
|
||||
public_party_size = min(int(project_config["max_party_size"]), party_size + 1)
|
||||
public_body = {"phone": public_phone, "last_name": "游客", "honorific": "游客", "party_size": public_party_size}
|
||||
public_key = str(uuid.uuid4())
|
||||
public_create = client.request(
|
||||
"POST",
|
||||
f"/api/public/projects/{project_id}/tickets",
|
||||
public_body,
|
||||
key=public_key,
|
||||
expected=201,
|
||||
)
|
||||
public_ticket = public_create["ticket"]
|
||||
assert public_ticket["ticket_number"]
|
||||
assert public_ticket["party_size"] == public_party_size
|
||||
assert public_ticket["phone_last4"] == public_phone[-4:]
|
||||
public_keys = mapping_keys(public_ticket)
|
||||
assert "phone" not in public_keys
|
||||
assert "last_name" not in public_keys
|
||||
assert "honorific" not in public_keys
|
||||
public_replay = client.request(
|
||||
"POST",
|
||||
f"/api/public/projects/{project_id}/tickets",
|
||||
public_body,
|
||||
key=public_key,
|
||||
expected=201,
|
||||
)
|
||||
assert public_replay["ticket"]["id"] == public_ticket["id"]
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user