feat: create and list orders as Deck cards
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V57jSQPqwkGG8BuAXg59X5
This commit is contained in:
parent
2313c3c490
commit
21e63685c9
|
|
@ -519,6 +519,43 @@ async def _ensure_deck_board() -> dict:
|
||||||
return _deck_cache
|
return _deck_cache
|
||||||
|
|
||||||
|
|
||||||
|
async def create_order(customer: str, description: str, due_date: str = None) -> dict:
|
||||||
|
board = await _ensure_deck_board()
|
||||||
|
stack_id = board["stacks"]["Offen"]
|
||||||
|
|
||||||
|
body = {"title": customer, "description": description, "type": "plain", "order": 999}
|
||||||
|
if due_date:
|
||||||
|
body["duedate"] = _as_calendar_local(datetime.fromisoformat(due_date)).isoformat()
|
||||||
|
|
||||||
|
card = await _deck_request("POST", f"/boards/{board['board_id']}/stacks/{stack_id}/cards", body)
|
||||||
|
return {
|
||||||
|
"id": card["id"],
|
||||||
|
"customer": customer,
|
||||||
|
"description": description,
|
||||||
|
"status": "Offen",
|
||||||
|
"due_date": due_date,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def list_orders(status: str = None) -> list:
|
||||||
|
board = await _ensure_deck_board()
|
||||||
|
stacks = await _deck_request("GET", f"/boards/{board['board_id']}/stacks")
|
||||||
|
|
||||||
|
orders = []
|
||||||
|
for stack in stacks:
|
||||||
|
if status and stack["title"] != status:
|
||||||
|
continue
|
||||||
|
for card in stack.get("cards", []):
|
||||||
|
orders.append({
|
||||||
|
"id": card["id"],
|
||||||
|
"customer": card["title"],
|
||||||
|
"description": card.get("description", ""),
|
||||||
|
"status": stack["title"],
|
||||||
|
"due_date": card.get("duedate"),
|
||||||
|
})
|
||||||
|
return orders
|
||||||
|
|
||||||
|
|
||||||
def _decode_mime_words(value: str) -> str:
|
def _decode_mime_words(value: str) -> str:
|
||||||
parts = decode_header(value)
|
parts = decode_header(value)
|
||||||
decoded = []
|
decoded = []
|
||||||
|
|
|
||||||
|
|
@ -141,3 +141,77 @@ async def test_ensure_deck_board_caches_after_first_lookup():
|
||||||
|
|
||||||
mock_req.assert_not_called()
|
mock_req.assert_not_called()
|
||||||
assert board["board_id"] == 10
|
assert board["board_id"] == 10
|
||||||
|
|
||||||
|
|
||||||
|
_BOARD = {"board_id": 10, "stacks": {"Offen": 1, "In Arbeit": 2, "Erledigt": 3}}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_order_creates_card_in_offen_stack():
|
||||||
|
with patch.object(main, "_ensure_deck_board", new=AsyncMock(return_value=_BOARD)), patch.object(
|
||||||
|
main, "_deck_request", new=AsyncMock(return_value={"id": 55})
|
||||||
|
) as mock_req:
|
||||||
|
order = await main.create_order("Feuerwehr Onza", "10x Handschuhe Groesse L")
|
||||||
|
|
||||||
|
assert order == {
|
||||||
|
"id": 55,
|
||||||
|
"customer": "Feuerwehr Onza",
|
||||||
|
"description": "10x Handschuhe Groesse L",
|
||||||
|
"status": "Offen",
|
||||||
|
"due_date": None,
|
||||||
|
}
|
||||||
|
method, path, body = mock_req.call_args[0]
|
||||||
|
assert method == "POST"
|
||||||
|
assert path == "/boards/10/stacks/1/cards"
|
||||||
|
assert body["title"] == "Feuerwehr Onza"
|
||||||
|
assert body["description"] == "10x Handschuhe Groesse L"
|
||||||
|
assert "duedate" not in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_order_localizes_due_date():
|
||||||
|
with patch.object(main, "_ensure_deck_board", new=AsyncMock(return_value=_BOARD)), patch.object(
|
||||||
|
main, "_deck_request", new=AsyncMock(return_value={"id": 55})
|
||||||
|
) as mock_req:
|
||||||
|
await main.create_order("Feuerwehr Onza", "Helme", due_date="2026-09-20")
|
||||||
|
|
||||||
|
_, _, body = mock_req.call_args[0]
|
||||||
|
assert body["duedate"] == datetime(2026, 9, 20, tzinfo=main.CALENDAR_TIMEZONE).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_orders_flattens_cards_across_stacks():
|
||||||
|
stacks_response = [
|
||||||
|
{"id": 1, "title": "Offen", "cards": [
|
||||||
|
{"id": 55, "title": "Feuerwehr Onza", "description": "Helme", "duedate": None}
|
||||||
|
]},
|
||||||
|
{"id": 2, "title": "In Arbeit", "cards": []},
|
||||||
|
{"id": 3, "title": "Erledigt", "cards": [
|
||||||
|
{"id": 56, "title": "Musterfirma", "description": "Stiefel", "duedate": "2026-09-20T00:00:00+02:00"}
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
with patch.object(main, "_ensure_deck_board", new=AsyncMock(return_value=_BOARD)), patch.object(
|
||||||
|
main, "_deck_request", new=AsyncMock(return_value=stacks_response)
|
||||||
|
):
|
||||||
|
orders = await main.list_orders()
|
||||||
|
|
||||||
|
assert len(orders) == 2
|
||||||
|
assert orders[0] == {
|
||||||
|
"id": 55, "customer": "Feuerwehr Onza", "description": "Helme", "status": "Offen", "due_date": None
|
||||||
|
}
|
||||||
|
assert orders[1]["status"] == "Erledigt"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_orders_filters_by_status():
|
||||||
|
stacks_response = [
|
||||||
|
{"id": 1, "title": "Offen", "cards": [{"id": 55, "title": "A", "description": "", "duedate": None}]},
|
||||||
|
{"id": 2, "title": "In Arbeit", "cards": []},
|
||||||
|
{"id": 3, "title": "Erledigt", "cards": [{"id": 56, "title": "B", "description": "", "duedate": None}]},
|
||||||
|
]
|
||||||
|
with patch.object(main, "_ensure_deck_board", new=AsyncMock(return_value=_BOARD)), patch.object(
|
||||||
|
main, "_deck_request", new=AsyncMock(return_value=stacks_response)
|
||||||
|
):
|
||||||
|
orders = await main.list_orders(status="Erledigt")
|
||||||
|
|
||||||
|
assert [o["id"] for o in orders] == [56]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue