Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import UTC, datetime

from fastapi import FastAPI, HTTPException, Query, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
Expand Down Expand Up @@ -272,14 +274,33 @@ def get_rmas():
return db_get("rmas", "order=created_at.desc")


# Resolution statuses that end an RMA's lifecycle. Moving into one of these
# stamps rmas.closed_at; moving back out (reopening) clears it.
RMA_CLOSED_STATUSES = {"Completed", "Cancelled"}


def utc_now_iso() -> str:
return datetime.now(UTC).isoformat()


@app.post("/rmas")
def create_rma(r: RMA):
return db_post("rmas", model_data(r))
data = model_data(r)
data["closed_at"] = utc_now_iso() if data["resolution_status"] in RMA_CLOSED_STATUSES else None
return db_post("rmas", data)


@app.put("/rmas/{id}")
def update_rma(id: str, r: RMA):
return db_patch("rmas", id, model_data(r))
data = model_data(r)
if data["resolution_status"] in RMA_CLOSED_STATUSES:
# Keep the original close time when an already-closed RMA is edited again.
old = db_get("rmas", f"id=eq.{escape_filter_value(id)}&select=closed_at")
if not (old and old[0].get("closed_at")):
data["closed_at"] = utc_now_iso()
else:
data["closed_at"] = None
return db_patch("rmas", id, data)


# --- Dashboard ---
Expand Down
61 changes: 61 additions & 0 deletions backend/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import sys
import types
from datetime import datetime
from unittest.mock import MagicMock
from urllib.parse import quote, unquote

Expand Down Expand Up @@ -507,6 +508,66 @@ def test_rma_routes_delegate_to_database(client, monkeypatch):
assert calls[1][0:3] == ("patch", "rmas", "r1")


def _capture_rma_writes(main, monkeypatch, existing_closed_at=None):
calls = []
monkeypatch.setattr(
main, "db_get", lambda table, params="": [{"closed_at": existing_closed_at}]
)
monkeypatch.setattr(main, "db_post", lambda table, data: calls.append(data) or data)
monkeypatch.setattr(main, "db_patch", lambda table, row_id, data: calls.append(data) or data)
return calls


def test_create_rma_sets_closed_at_only_for_closed_status(client, monkeypatch):
test_client, main = client
calls = _capture_rma_writes(main, monkeypatch)

test_client.post("/rmas", json={"ticket_id": "t1", "rma_number": "RMA-1"})
test_client.post(
"/rmas",
json={"ticket_id": "t1", "rma_number": "RMA-2", "resolution_status": "Completed"},
)

assert calls[0]["closed_at"] is None
assert datetime.fromisoformat(calls[1]["closed_at"]).tzinfo is not None


def test_update_rma_stamps_closed_at_when_closing(client, monkeypatch):
test_client, main = client
calls = _capture_rma_writes(main, monkeypatch, existing_closed_at=None)

test_client.put(
"/rmas/r1",
json={"ticket_id": "t1", "rma_number": "RMA-1", "resolution_status": "Cancelled"},
)

assert datetime.fromisoformat(calls[0]["closed_at"]).tzinfo is not None


def test_update_rma_keeps_existing_closed_at(client, monkeypatch):
test_client, main = client
calls = _capture_rma_writes(main, monkeypatch, existing_closed_at="2026-01-01T00:00:00+00:00")

test_client.put(
"/rmas/r1",
json={"ticket_id": "t1", "rma_number": "RMA-1", "resolution_status": "Completed"},
)

assert "closed_at" not in calls[0]


def test_update_rma_clears_closed_at_when_reopened(client, monkeypatch):
test_client, main = client
calls = _capture_rma_writes(main, monkeypatch, existing_closed_at="2026-01-01T00:00:00+00:00")

test_client.put(
"/rmas/r1",
json={"ticket_id": "t1", "rma_number": "RMA-1", "resolution_status": "In Progress"},
)

assert calls[0]["closed_at"] is None


def test_dashboard_counts_ticket_and_rma_statuses(client, monkeypatch):
test_client, main = client

Expand Down
Loading